--- /dev/null
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+/**
+ * Generate Ed25519 field element operations for WebAssembly API. Specifically,
+ * it outputs `fe.ts`, an AssemblyScript file that must be compiled as part of a
+ * WASM module.
+ *
+ * The final output file should be checked separately for correctness using the
+ * AssemblyScript compiler and type checker.
+ */
+
+import { writeFile } from 'node:fs/promises'
+
+const FE = `//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+/**
+ * Arithmetic on field elements. These are integers in the range
+ *
+ * \`0 ≤ n < p\`
+ *
+ * where
+ *
+ * \`p = 2²⁵⁵-19\`
+ *
+ * They are represented as 10 limbs of alternating 25- and 26-bit values so that
+ *
+ * \`n = 2⁰a[0] + 2²⁶a[1] + 2⁷⁷a[2] ... + 2²³⁰a[9]\`
+ */
+
+import { load_3, load_4, sodium_is_zero } from './utils'
+
+/**
+ * 10x 25- and 26-bit array of values representing a large integer \`n mod p\`. In
+ * this implementation, 12 elements are allocated instead of 10, and this is for
+ * two reasons:
+ * 1. There is no vectorized 64-bit data type in Wasm, only 128-bit, so \`v128\`
+ * must be used in the form of \`i32x4\` to achieve SIMD parallelism, and
+ * 2. \`i32x4.load\` will load lanes \`[0, 1]\` for elements [8,9] just fine but
+ * risks retrieving garbage data for lanes \`[2, 3]\` from some other chunk of
+ * memory if it is not allocated to this FieldElement, so elements [10,11] are
+ * reserved, initialized to zero, and simply ignored throughout computation.
+ */
+export type FieldElement = StaticArray<i32>
+export function fe (): FieldElement {
+ return new StaticArray<i32>(12)
+}
+
+/**
+ * Set all values of a FieldElement to zero.
+ *
+ * @param h FieldElement destination
+ */
+//@ts-expect-error
+@inline
+export function fe_0 (h: FieldElement): void {
+ memory.fill(changetype<usize>(h), 0, 48)
+}
+
+/**
+ * Set the first limb of a FieldElement to 1 and all subsequent limbs to zero.
+ *
+ * @param h FieldElement destination
+ */
+//@ts-expect-error
+@inline
+export function fe_1 (h: FieldElement): void {
+ fe_0(h)
+ store<i32>(changetype<usize>(h), 1, 0)
+}
+
+/**
+ * Add two FieldElements and store the sum.
+ *
+ * @param h FieldElement sum destination
+ * @param f FieldElement summand source
+ * @param g FieldElement summand source
+ */
+//@ts-expect-error
+@inline
+export function fe_add (h: FieldElement, f: FieldElement, g: FieldElement): void {
+ const h_ptr = changetype<usize>(h)
+ const f_ptr = changetype<usize>(f)
+ const g_ptr = changetype<usize>(g)
+ v128.store(h_ptr, v128.add<i32>(v128.load(f_ptr, 0), v128.load(g_ptr, 0)), 0)
+ v128.store(h_ptr, v128.add<i32>(v128.load(f_ptr, 16), v128.load(g_ptr, 16)), 16)
+ v128.store_lane<i64>(h_ptr, v128.add<i32>(v128.load(f_ptr, 32), v128.load(g_ptr, 32)), 0, 32)
+}
+
+/**
+ * Conditionally copy values from one FieldElement to another.
+ *
+ * @param {FieldElement} f destination
+ * @param {FieldElement} g source
+ * @param {u64} b Copy \`g\` into \`f\` if 1, copy \`f\` into \`f\` if 0
+ */
+//@ts-expect-error
+@inline
+export function fe_cmov (f: FieldElement, g: FieldElement, b: u64): void {
+ const f_ptr = changetype<usize>(f)
+ const g_ptr = changetype<usize>(g)
+ const c = v128.splat<u64>(0 - b)
+ v128.store(f_ptr, v128.bitselect(v128.load(g_ptr), v128.load(f_ptr, 0), c))
+ v128.store(f_ptr, v128.bitselect(v128.load(g_ptr, 16), v128.load(f_ptr, 16), c), 16)
+ v128.store_lane<u64>(f_ptr, v128.bitselect(v128.load(g_ptr, 32), v128.load(f_ptr, 32), c), 0, 32)
+}
+
+/**
+ * Copy values from one FieldElement to another.
+ *
+ * @param {FieldElement} h destination
+ * @param {FieldElement} f source
+ */
+//@ts-expect-error
+@inline
+export function fe_copy (h: FieldElement, f: FieldElement): void {
+ memory.copy(changetype<usize>(h), changetype<usize>(f), 40)
+}
+
+/**
+ * Add a FieldElement to itself and store the sum.
+ *
+ * @param h FieldElement sum destination
+ * @param f FieldElement summand source
+ */
+//@ts-expect-error
+@inline
+export function fe_dbl (h: FieldElement, f: FieldElement): void {
+ const h_ptr = changetype<usize>(h)
+ const f_ptr = changetype<usize>(f)
+ v128.store(h_ptr, v128.shl<i32>(v128.load(f_ptr, 0), 1), 0)
+ v128.store(h_ptr, v128.shl<i32>(v128.load(f_ptr, 16), 1), 16)
+ v128.store_lane<i64>(h_ptr, v128.shl<i32>(v128.load(f_ptr, 32), 1), 0, 32)
+}
+
+/**
+ * Ignores top bit of s.
+ */
+export function fe_frombytes (h: FieldElement, s: StaticArray<u8>): void {
+ let h0: i64 = load_4(s, 0)
+ let h1: i64 = load_3(s, 4) << 6
+ let h2: i64 = load_3(s, 7) << 5
+ let h3: i64 = load_3(s, 10) << 3
+ let h4: i64 = load_3(s, 13) << 2
+ let h5: i64 = load_4(s, 16)
+ let h6: i64 = load_3(s, 20) << 7
+ let h7: i64 = load_3(s, 23) << 5
+ let h8: i64 = load_3(s, 26) << 4
+ let h9: i64 = (load_3(s, 29) & 8388607) << 2
+
+ let carry0: i64
+ let carry1: i64
+ let carry2: i64
+ let carry3: i64
+ let carry4: i64
+ let carry5: i64
+ let carry6: i64
+ let carry7: i64
+ let carry8: i64
+ let carry9: i64
+
+ carry9 = (h9 + i64(1 << 24)) >> 25
+ h0 += carry9 * 19
+ h9 -= carry9 << 25
+ carry1 = (h1 + i64(1 << 24)) >> 25
+ h2 += carry1
+ h1 -= carry1 << 25
+ carry3 = (h3 + i64(1 << 24)) >> 25
+ h4 += carry3
+ h3 -= carry3 << 25
+ carry5 = (h5 + i64(1 << 24)) >> 25
+ h6 += carry5
+ h5 -= carry5 << 25
+ carry7 = (h7 + i64(1 << 24)) >> 25
+ h8 += carry7
+ h7 -= carry7 << 25
+
+ carry0 = (h0 + i64(1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+ carry2 = (h2 + i64(1 << 25)) >> 26
+ h3 += carry2
+ h2 -= carry2 << 26
+ carry4 = (h4 + i64(1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+ carry6 = (h6 + i64(1 << 25)) >> 26
+ h7 += carry6
+ h6 -= carry6 << 26
+ carry8 = (h8 + i64(1 << 25)) >> 26
+ h9 += carry8
+ h8 -= carry8 << 26
+
+ const h_ptr: usize = changetype<usize>(h)
+ store<i32>(h_ptr, h0, 0)
+ store<i32>(h_ptr, h1, 4)
+ store<i32>(h_ptr, h2, 8)
+ store<i32>(h_ptr, h3, 12)
+ store<i32>(h_ptr, h4, 16)
+ store<i32>(h_ptr, h5, 20)
+ store<i32>(h_ptr, h6, 24)
+ store<i32>(h_ptr, h7, 28)
+ store<i32>(h_ptr, h8, 32)
+ store<i32>(h_ptr, h9, 36)
+}
+
+const t0: FieldElement = fe()
+const t1: FieldElement = fe()
+const t2: FieldElement = fe()
+const t3: FieldElement = fe()
+/**
+ * Inversion - sets \`out\` = 0 if \`z\` == 0
+ */
+export function fe_invert (out: FieldElement, z: FieldElement): void {
+ fe_sq(t0, z)
+ fe_sq(t1, t0)
+ fe_sq(t1, t1)
+ fe_mul(t1, z, t1)
+ fe_mul(t0, t0, t1)
+ fe_sq(t2, t0)
+ fe_mul(t1, t1, t2)
+ fe_sq(t2, t1)
+ for (let i = 1; i < 5; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t1, t2, t1)
+ fe_sq(t2, t1)
+ for (let i = 1; i < 10; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t2, t2, t1)
+ fe_sq(t3, t2)
+ for (let i = 1; i < 20; ++i) {
+ fe_sq(t3, t3)
+ }
+ fe_mul(t2, t3, t2)
+ for (let i = 1; i < 11; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t1, t2, t1)
+ fe_sq(t2, t1)
+ for (let i = 1; i < 50; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t2, t2, t1)
+ fe_sq(t3, t2)
+ for (let i = 1; i < 100; ++i) {
+ fe_sq(t3, t3)
+ }
+ fe_mul(t2, t3, t2)
+ for (let i = 1; i < 51; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t1, t2, t1)
+ for (let i = 1; i < 6; ++i) {
+ fe_sq(t1, t1)
+ }
+ fe_mul(out, t1, t0)
+}
+
+const fe_isnegative_t = fe()
+/**
+ * return 1 if f is in {1,3,5,...,q-2}
+ * return 0 if f is in {0,2,4,...,q-1}
+ *
+ * Preconditions:
+ * |f| bounded by 1.1x2²⁶,1.1x2²⁵,1.1x2²⁶,1.1x2²⁵,etc.
+ */
+//@ts-expect-error
+@inline
+export function fe_isnegative (f: FieldElement): u8 {
+ const t = fe_isnegative_t
+ fe_reduce(t, f)
+ return load<u8>(changetype<usize>(t), 0) & 1
+}
+
+const fe_iszero_s = new StaticArray<u8>(32)
+/**
+ * return 1 if f == 0
+ * return 0 if f != 0
+ *
+ * Preconditions:
+ * |f| bounded by 1.1x2²⁶,1.1x2²⁵,1.1x2²⁶,1.1x2²⁵,etc.
+ */
+//@ts-expect-error
+@inline
+export function fe_iszero (f: FieldElement): u8 {
+ const s = fe_iszero_s
+ fe_tobytes(s, f)
+ return sodium_is_zero(s, 32)
+}
+
+/**
+ * Multiply two FieldElements and store the product.
+ *
+ * @param h FieldElement product destination
+ * @param f FieldElement multiplicand source
+ * @param g FieldElement multiplicand source
+ */
+export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void {
+ const f_ptr: usize = changetype<usize>(f)
+ const g_ptr: usize = changetype<usize>(g)
+ const g0123: v128 = v128.load(g_ptr, 0)
+ const g4567: v128 = v128.load(g_ptr, 16)
+ const g89xx: v128 = v128.load(g_ptr, 32)
+
+ // mask to select even lane from first vector and odd lane from second
+ const m: v128 = i32x4(-1, 0, -1, 0)
+
+ // mask to double odd-on-odd indexes
+ const odds: v128 = i32x4(0, -1, 0, -1)
+
+ // factor of 19 for wrap from h9 to h0
+ const v19: v128 = i32x4.splat(19)
+ const g0123_19: v128 = i32x4.mul(g0123, v19)
+ const g4567_19: v128 = i32x4.mul(g4567, v19)
+ const g89xx_19: v128 = i32x4.mul(g89xx, v19)
+
+ // f[0]
+ let fi: i32 = load<i32>(f_ptr, 0)
+ let fv: v128 = i32x4.splat(fi)
+
+ let h01: v128 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ let h23: v128 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ let h45: v128 = i64x2.extmul_low_i32x4_s(fv, g4567)
+ let h67: v128 = i64x2.extmul_high_i32x4_s(fv, g4567)
+ let h89: v128 = i64x2.extmul_low_i32x4_s(fv, g89xx)
+
+ // f[1]
+ fi = load<i32>(f_ptr, 4)
+ fv = i32x4.splat(fi)
+ fv = i32x4.add(fv, v128.and(fv, odds))
+
+ let h12: v128 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ let h34: v128 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ let h56: v128 = i64x2.extmul_low_i32x4_s(fv, g4567)
+ let h78: v128 = i64x2.extmul_high_i32x4_s(fv, g4567)
+ let h90: v128 = i64x2.extmul_low_i32x4_s(fv, v128.bitselect(g89xx, g89xx_19, m))
+
+ // f[2]
+ fi = load<i32>(f_ptr, 8)
+ fv = i32x4.splat(fi)
+
+ let t0: v128 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ let t1: v128 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ let t2: v128 = i64x2.extmul_low_i32x4_s(fv, g4567)
+ let t3: v128 = i64x2.extmul_high_i32x4_s(fv, g4567)
+ let t4: v128 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h23 = i64x2.add(h23, t0)
+ h45 = i64x2.add(h45, t1)
+ h67 = i64x2.add(h67, t2)
+ h89 = i64x2.add(h89, t3)
+ h01 = i64x2.add(h01, t4)
+
+ // f[3]
+ fi = load<i32>(f_ptr, 12)
+ fv = i32x4.splat(fi)
+ fv = i32x4.add(fv, v128.and(fv, odds))
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ t1 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ t2 = i64x2.extmul_low_i32x4_s(fv, g4567)
+ t3 = i64x2.extmul_high_i32x4_s(fv, v128.bitselect(g4567, g4567_19, m))
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h34 = i64x2.add(h34, t0)
+ h56 = i64x2.add(h56, t1)
+ h78 = i64x2.add(h78, t2)
+ h90 = i64x2.add(h90, t3)
+ h12 = i64x2.add(h12, t4)
+
+ // f[4]
+ fi = load<i32>(f_ptr, 16)
+ fv = i32x4.splat(fi)
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ t1 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ t2 = i64x2.extmul_low_i32x4_s(fv, g4567)
+ t3 = i64x2.extmul_high_i32x4_s(fv, g4567_19)
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h45 = i64x2.add(h45, t0)
+ h67 = i64x2.add(h67, t1)
+ h89 = i64x2.add(h89, t2)
+ h01 = i64x2.add(h01, t3)
+ h23 = i64x2.add(h23, t4)
+
+ // f[5]
+ fi = load<i32>(f_ptr, 20)
+ fv = i32x4.splat(fi)
+ fv = i32x4.add(fv, v128.and(fv, odds))
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ t1 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ t2 = i64x2.extmul_low_i32x4_s(fv, v128.bitselect(g4567, g4567_19, m))
+ t3 = i64x2.extmul_high_i32x4_s(fv, g4567_19)
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h56 = i64x2.add(h56, t0)
+ h78 = i64x2.add(h78, t1)
+ h90 = i64x2.add(h90, t2)
+ h12 = i64x2.add(h12, t3)
+ h34 = i64x2.add(h34, t4)
+
+ // f[6]
+ fi = load<i32>(f_ptr, 24)
+ fv = i32x4.splat(fi)
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ t1 = i64x2.extmul_high_i32x4_s(fv, g0123)
+ t2 = i64x2.extmul_low_i32x4_s(fv, g4567_19)
+ t3 = i64x2.extmul_high_i32x4_s(fv, g4567_19)
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h67 = i64x2.add(h67, t0)
+ h89 = i64x2.add(h89, t1)
+ h01 = i64x2.add(h01, t2)
+ h23 = i64x2.add(h23, t3)
+ h45 = i64x2.add(h45, t4)
+
+ // f[7]
+ fi = load<i32>(f_ptr, 28)
+ fv = i32x4.splat(fi)
+ fv = i32x4.add(fv, v128.and(fv, odds))
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ t1 = i64x2.extmul_high_i32x4_s(fv, v128.bitselect(g0123, g0123_19, m))
+ t2 = i64x2.extmul_low_i32x4_s(fv, g4567_19)
+ t3 = i64x2.extmul_high_i32x4_s(fv, g4567_19)
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h78 = i64x2.add(h78, t0)
+ h90 = i64x2.add(h90, t1)
+ h12 = i64x2.add(h12, t2)
+ h34 = i64x2.add(h34, t3)
+ h56 = i64x2.add(h56, t4)
+
+ // f[8]
+ fi = load<i32>(f_ptr, 32)
+ fv = i32x4.splat(fi)
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, g0123)
+ t1 = i64x2.extmul_high_i32x4_s(fv, g0123_19)
+ t2 = i64x2.extmul_low_i32x4_s(fv, g4567_19)
+ t3 = i64x2.extmul_high_i32x4_s(fv, g4567_19)
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h89 = i64x2.add(h89, t0)
+ h01 = i64x2.add(h01, t1)
+ h23 = i64x2.add(h23, t2)
+ h45 = i64x2.add(h45, t3)
+ h67 = i64x2.add(h67, t4)
+
+ // f[9]
+ fi = load<i32>(f_ptr, 36)
+ fv = i32x4.splat(fi)
+ fv = i32x4.add(fv, v128.and(fv, odds))
+
+ t0 = i64x2.extmul_low_i32x4_s(fv, v128.bitselect(g0123, g0123_19, m))
+ t1 = i64x2.extmul_high_i32x4_s(fv, g0123_19)
+ t2 = i64x2.extmul_low_i32x4_s(fv, g4567_19)
+ t3 = i64x2.extmul_high_i32x4_s(fv, g4567_19)
+ t4 = i64x2.extmul_low_i32x4_s(fv, g89xx_19)
+
+ h90 = i64x2.add(h90, t0)
+ h12 = i64x2.add(h12, t1)
+ h34 = i64x2.add(h34, t2)
+ h56 = i64x2.add(h56, t3)
+ h78 = i64x2.add(h78, t4)
+
+ // extract scalars from vectors
+ let h0: i64 = i64x2.extract_lane(h90, 1) + i64x2.extract_lane(h01, 0)
+ let h1: i64 = i64x2.extract_lane(h01, 1) + i64x2.extract_lane(h12, 0)
+ let h2: i64 = i64x2.extract_lane(h12, 1) + i64x2.extract_lane(h23, 0)
+ let h3: i64 = i64x2.extract_lane(h23, 1) + i64x2.extract_lane(h34, 0)
+ let h4: i64 = i64x2.extract_lane(h34, 1) + i64x2.extract_lane(h45, 0)
+ let h5: i64 = i64x2.extract_lane(h45, 1) + i64x2.extract_lane(h56, 0)
+ let h6: i64 = i64x2.extract_lane(h56, 1) + i64x2.extract_lane(h67, 0)
+ let h7: i64 = i64x2.extract_lane(h67, 1) + i64x2.extract_lane(h78, 0)
+ let h8: i64 = i64x2.extract_lane(h78, 1) + i64x2.extract_lane(h89, 0)
+ let h9: i64 = i64x2.extract_lane(h89, 1) + i64x2.extract_lane(h90, 0)
+
+ // extract scalars from vectors during first carry
+ let carry0: i64
+ let carry1: i64
+ let carry2: i64
+ let carry3: i64
+ let carry4: i64
+ let carry5: i64
+ let carry6: i64
+ let carry7: i64
+ let carry8: i64
+ let carry9: i64
+
+ carry0 = (h0 + (1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+ carry4 = (h4 + (1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+
+ carry1 = (h1 + (1 << 24)) >> 25
+ h2 += carry1
+ h1 -= carry1 << 25
+ carry5 = (h5 + (1 << 24)) >> 25
+ h6 += carry5
+ h5 -= carry5 << 25
+
+ carry2 = (h2 + (1 << 25)) >> 26
+ h3 += carry2
+ h2 -= carry2 << 26
+ carry6 = (h6 + (1 << 25)) >> 26
+ h7 += carry6
+ h6 -= carry6 << 26
+
+ carry3 = (h3 + (1 << 24)) >> 25
+ h4 += carry3
+ h3 -= carry3 << 25
+ carry7 = (h7 + (1 << 24)) >> 25
+ h8 += carry7
+ h7 -= carry7 << 25
+
+ carry4 = (h4 + (1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+ carry8 = (h8 + (1 << 25)) >> 26
+ h9 += carry8
+ h8 -= carry8 << 26
+
+ carry9 = (h9 + (1 << 24)) >> 25
+ h0 += carry9 * 19
+ h9 -= carry9 << 25
+
+ carry0 = (h0 + (1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+
+ // assign results to output
+ const h_ptr: usize = changetype<usize>(h)
+ store<i32>(h_ptr, h0, 0)
+ store<i32>(h_ptr, h1, 4)
+ store<i32>(h_ptr, h2, 8)
+ store<i32>(h_ptr, h3, 12)
+ store<i32>(h_ptr, h4, 16)
+ store<i32>(h_ptr, h5, 20)
+ store<i32>(h_ptr, h6, 24)
+ store<i32>(h_ptr, h7, 28)
+ store<i32>(h_ptr, h8, 32)
+ store<i32>(h_ptr, h9, 36)
+}
+
+/**
+ * Negate the values of a FieldElement and store the result.
+ *
+ * @param {FieldElement} h result destination
+ * @param {FieldElement} f operand source
+ */
+//@ts-expect-error
+@inline
+export function fe_neg (h: FieldElement, f: FieldElement): void {
+ const h_ptr = changetype<usize>(h)
+ const f_ptr = changetype<usize>(f)
+ v128.store(h_ptr, v128.neg<i32>(v128.load(f_ptr)))
+ v128.store(h_ptr, v128.neg<i32>(v128.load(f_ptr, 16)), 16)
+ v128.store_lane<i64>(h_ptr, v128.neg<i32>(v128.load(f_ptr, 32)), 0, 32)
+}
+
+const fe_pow22523_t0: FieldElement = fe()
+const fe_pow22523_t1: FieldElement = fe()
+const fe_pow22523_t2: FieldElement = fe()
+/**
+ * returns z^((p-5)/8) = z^(2²⁵²-3)
+ * used to compute square roots since we have p=5 (mod 8); see Cohen and Frey.
+ */
+export function fe_pow22523 (out: FieldElement, z: FieldElement): void {
+ const t0 = fe_pow22523_t0
+ const t1 = fe_pow22523_t1
+ const t2 = fe_pow22523_t2
+
+ fe_sq(t0, z)
+ fe_sq(t1, t0)
+ fe_sq(t1, t1)
+ fe_mul(t1, z, t1)
+ fe_mul(t0, t0, t1)
+ fe_sq(t0, t0)
+ fe_mul(t0, t1, t0)
+ fe_sq(t1, t0)
+ for (let i = 1; i < 5; ++i) {
+ fe_sq(t1, t1)
+ }
+ fe_mul(t0, t1, t0)
+ fe_sq(t1, t0)
+ for (let i = 1; i < 10; ++i) {
+ fe_sq(t1, t1)
+ }
+ fe_mul(t1, t1, t0)
+ fe_sq(t2, t1)
+ for (let i = 1; i < 20; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t1, t2, t1)
+ for (let i = 1; i < 11; ++i) {
+ fe_sq(t1, t1)
+ }
+ fe_mul(t0, t1, t0)
+ fe_sq(t1, t0)
+ for (let i = 1; i < 50; ++i) {
+ fe_sq(t1, t1)
+ }
+ fe_mul(t1, t1, t0)
+ fe_sq(t2, t1)
+ for (let i = 1; i < 100; ++i) {
+ fe_sq(t2, t2)
+ }
+ fe_mul(t1, t2, t1)
+ for (let i = 1; i < 51; ++i) {
+ fe_sq(t1, t1)
+ }
+ fe_mul(t0, t1, t0)
+ fe_sq(t0, t0)
+ fe_sq(t0, t0)
+ fe_mul(out, t0, z)
+}
+
+/**
+ * Preconditions:
+ * |h| bounded by 1.1x2²⁶,1.1x2²⁵,1.1x2²⁶,1.1x2²⁵,etc.
+ *
+ * Write p=2²⁵⁵-19; q=floor(h/p).
+ * Basic claim: q = floor(2⁻²⁵⁵(h + 19 2⁻²⁵h9 + 2⁻¹)).
+ *
+ * Proof:
+ * Have |h|≤p so |q|≤1 so |19² 2⁻²⁵⁵ q|<1/4.
+ * Also have |h-2²³⁰ h9|<2²³¹ so |19 2⁻²⁵⁵(h-2²³⁰ h9)|<1/4.
+ *
+ * Write y=2⁻¹-19² 2⁻²⁵⁵q-19 2⁻²⁵⁵(h-2²³⁰ h9).
+ * Then 0<y<1.
+ *
+ * Write r=h-pq.
+ * Have 0 ≤ r ≤ p-1 = 2²⁵⁵-20.
+ * Thus 0 ≤ r+19(2⁻²⁵⁵)r < r+19(2⁻²⁵⁵)2²⁵⁵ ≤ 2²⁵⁵-1.
+ *
+ * Write x=r+19(2⁻²⁵⁵)r+y.
+ * Then 0 < x < 2²⁵⁵ so floor(2⁻²⁵⁵x) = 0 so floor(q+2⁻²⁵⁵x) = q.
+ *
+ * Have q+2⁻²⁵⁵x = 2⁻²⁵⁵(h + 19 2⁻²⁵ h9 + 2⁻¹)
+ * so floor(2⁻²⁵⁵(h + 19 2⁻²⁵ h9 + 2⁻¹)) = q.
+ */
+export function fe_reduce (h: FieldElement, f: FieldElement): void {
+ const f_ptr: usize = changetype<usize>(f)
+ let f0: i32 = load<i32>(f_ptr, 0)
+ let f1: i32 = load<i32>(f_ptr, 4)
+ let f2: i32 = load<i32>(f_ptr, 8)
+ let f3: i32 = load<i32>(f_ptr, 12)
+ let f4: i32 = load<i32>(f_ptr, 16)
+ let f5: i32 = load<i32>(f_ptr, 20)
+ let f6: i32 = load<i32>(f_ptr, 24)
+ let f7: i32 = load<i32>(f_ptr, 28)
+ let f8: i32 = load<i32>(f_ptr, 32)
+ let f9: i32 = load<i32>(f_ptr, 36)
+ let q: i32
+ let c0: i32, c1: i32, c2: i32, c3: i32, c4: i32, c5: i32, c6: i32, c7: i32, c8: i32, c9: i32
+
+ q = (19 * f9 + (1 << 24)) >> 25
+ q = (f0 + q) >> 26
+ q = (f1 + q) >> 25
+ q = (f2 + q) >> 26
+ q = (f3 + q) >> 25
+ q = (f4 + q) >> 26
+ q = (f5 + q) >> 25
+ q = (f6 + q) >> 26
+ q = (f7 + q) >> 25
+ q = (f8 + q) >> 26
+ q = (f9 + q) >> 25
+
+ /* Goal: Output h-(2²⁵⁵-19)q, which is between 0 and 2²⁵⁵-20. */
+ f0 += 19 * q
+ /* Goal: Output h-2²⁵⁵ q, which is between 0 and 2²⁵⁵-20. */
+
+ c0 = f0 >> 26
+ f1 += c0
+ f0 -= c0 << 26
+ c1 = f1 >> 25
+ f2 += c1
+ f1 -= c1 << 25
+ c2 = f2 >> 26
+ f3 += c2
+ f2 -= c2 << 26
+ c3 = f3 >> 25
+ f4 += c3
+ f3 -= c3 << 25
+ c4 = f4 >> 26
+ f5 += c4
+ f4 -= c4 << 26
+ c5 = f5 >> 25
+ f6 += c5
+ f5 -= c5 << 25
+ c6 = f6 >> 26
+ f7 += c6
+ f6 -= c6 << 26
+ c7 = f7 >> 25
+ f8 += c7
+ f7 -= c7 << 25
+ c8 = f8 >> 26
+ f9 += c8
+ f8 -= c8 << 26
+ c9 = f9 >> 25
+ f9 -= c9 << 25
+
+ const h_ptr: usize = changetype<usize>(h)
+ store<i32>(h_ptr, f0, 0)
+ store<i32>(h_ptr, f1, 4)
+ store<i32>(h_ptr, f2, 8)
+ store<i32>(h_ptr, f3, 12)
+ store<i32>(h_ptr, f4, 16)
+ store<i32>(h_ptr, f5, 20)
+ store<i32>(h_ptr, f6, 24)
+ store<i32>(h_ptr, f7, 28)
+ store<i32>(h_ptr, f8, 32)
+ store<i32>(h_ptr, f9, 36)
+}
+
+/**
+ * Square a FieldElement and store the result.
+ *
+ * Some iterations of the inner loop can be skipped since
+ * \`f[x] * f[y] + f[y] * f[x] = 2 * f[x] * f[y]\`
+ *
+ * \`\`\`
+ * // non-constant-time example
+ * for (let i = 0; i < 10; i++) {
+ * for (let j = i; j < 10; j++) {
+ * h[i + j] += f[i] * g[j]
+ * if (i < j) {
+ * h[i + j] *= 2
+ * }
+ * }
+ * }
+ * \`\`\`
+ *
+ * h = f * f
+ * Can overlap h with f.
+ *
+ * Preconditions:
+ * |f| bounded by 1.65*2²⁶,1.65*2²⁵,1.65*2²⁶,1.65*2²⁵,etc.
+ *
+ * Postconditions:
+ * |h| bounded by 1.01*2²⁵,1.01*2²⁴4,1.01*2²⁵,1.01*2²⁴,etc.
+ *
+ * @param h FieldElement power destination
+ * @param f FieldElement base source
+ */
+export function fe_sq (h: FieldElement, f: FieldElement): void {
+ const f_ptr: usize = changetype<usize>(f)
+ const f0: i64 = i64(load<i32>(f_ptr, 0))
+ const f1: i64 = i64(load<i32>(f_ptr, 4))
+ const f2: i64 = i64(load<i32>(f_ptr, 8))
+ const f3: i64 = i64(load<i32>(f_ptr, 12))
+ const f4: i64 = i64(load<i32>(f_ptr, 16))
+ const f5: i64 = i64(load<i32>(f_ptr, 20))
+ const f6: i64 = i64(load<i32>(f_ptr, 24))
+ const f7: i64 = i64(load<i32>(f_ptr, 28))
+ const f8: i64 = i64(load<i32>(f_ptr, 32))
+ const f9: i64 = i64(load<i32>(f_ptr, 36))
+
+ const f0_2: i64 = f0 * 2
+ const f1_2: i64 = f1 * 2
+ const f2_2: i64 = f2 * 2
+ const f3_2: i64 = f3 * 2
+ const f4_2: i64 = f4 * 2
+ const f5_2: i64 = f5 * 2
+ const f6_2: i64 = f6 * 2
+ const f7_2: i64 = f7 * 2
+
+ const f5_19: i64 = f5 * 19 /* 1.959375*2²⁹ */
+ const f6_19: i64 = f6 * 19 /* 1.959375*2³⁰ */
+ const f7_19: i64 = f7 * 19 /* 1.959375*2²⁹ */
+ const f8_19: i64 = f8 * 19 /* 1.959375*2³⁰ */
+ const f9_19: i64 = f9 * 19 /* 1.959375*2²⁹ */
+
+ const f7_38: i64 = f7 * 38 /* 1.959375*2³⁰ */
+ const f9_38: i64 = f9 * 38 /* 1.959375*2³⁰ */
+
+ let h0: i64 = f0 * f0
+ let h1: i64 = f0_2 * f1
+ let h2: i64 = f0_2 * f2
+ let h3: i64 = f0_2 * f3
+ let h4: i64 = f0_2 * f4
+ let h5: i64 = f0_2 * f5
+ let h6: i64 = f0_2 * f6
+ let h7: i64 = f0_2 * f7
+ let h8: i64 = f0_2 * f8
+ let h9: i64 = f0_2 * f9
+
+ h2 += f1_2 * f1
+ h3 += f1_2 * f2
+ h4 += f1_2 * f3_2
+ h5 += f1_2 * f4
+ h6 += f1_2 * f5_2
+ h7 += f1_2 * f6
+ h8 += f1_2 * f7_2
+ h9 += f1_2 * f8
+ h0 += f1_2 * f9_38
+
+ h4 += f2 * f2
+ h5 += f2_2 * f3
+ h6 += f2_2 * f4
+ h7 += f2_2 * f5
+ h8 += f2_2 * f6
+ h9 += f2_2 * f7
+ h0 += f2_2 * f8_19
+ h1 += f2_2 * f9_19
+
+ h6 += f3_2 * f3
+ h7 += f3_2 * f4
+ h8 += f3_2 * f5_2
+ h9 += f3_2 * f6
+ h0 += f3_2 * f7_38
+ h1 += f3_2 * f8_19
+ h2 += f3_2 * f9_38
+
+ h8 += f4 * f4
+ h9 += f4_2 * f5
+ h0 += f4_2 * f6_19
+ h1 += f4_2 * f7_19
+ h2 += f4_2 * f8_19
+ h3 += f4_2 * f9_19
+
+ h0 += f5_2 * f5_19
+ h1 += f5_2 * f6_19
+ h2 += f5_2 * f7_38
+ h3 += f5_2 * f8_19
+ h4 += f5_2 * f9_38
+
+ h2 += f6 * f6_19
+ h3 += f6_2 * f7_19
+ h4 += f6_2 * f8_19
+ h5 += f6_2 * f9_19
+
+ h4 += f7_2 * f7_19
+ h5 += f7_2 * f8_19
+ h6 += f7_2 * f9_38
+
+ h6 += f8 * f8_19
+ h7 += f8 * f9_38
+
+ h8 += f9 * f9_38
+
+ let carry0: i64
+ let carry1: i64
+ let carry2: i64
+ let carry3: i64
+ let carry4: i64
+ let carry5: i64
+ let carry6: i64
+ let carry7: i64
+ let carry8: i64
+ let carry9: i64
+
+ carry0 = (h0 + i64(1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+ carry4 = (h4 + i64(1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+
+ carry1 = (h1 + i64(1 << 24)) >> 25
+ h2 += carry1
+ h1 -= carry1 << 25
+ carry5 = (h5 + i64(1 << 24)) >> 25
+ h6 += carry5
+ h5 -= carry5 << 25
+
+ carry2 = (h2 + i64(1 << 25)) >> 26
+ h3 += carry2
+ h2 -= carry2 << 26
+ carry6 = (h6 + i64(1 << 25)) >> 26
+ h7 += carry6
+ h6 -= carry6 << 26
+
+ carry3 = (h3 + i64(1 << 24)) >> 25
+ h4 += carry3
+ h3 -= carry3 << 25
+ carry7 = (h7 + i64(1 << 24)) >> 25
+ h8 += carry7
+ h7 -= carry7 << 25
+
+ carry4 = (h4 + i64(1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+ carry8 = (h8 + i64(1 << 25)) >> 26
+ h9 += carry8
+ h8 -= carry8 << 26
+
+ carry9 = (h9 + i64(1 << 24)) >> 25
+ h0 += carry9 * 19
+ h9 -= carry9 << 25
+
+ carry0 = (h0 + i64(1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+
+ const h_ptr: usize = changetype<usize>(h)
+ store<i32>(h_ptr, h0, 0)
+ store<i32>(h_ptr, h1, 4)
+ store<i32>(h_ptr, h2, 8)
+ store<i32>(h_ptr, h3, 12)
+ store<i32>(h_ptr, h4, 16)
+ store<i32>(h_ptr, h5, 20)
+ store<i32>(h_ptr, h6, 24)
+ store<i32>(h_ptr, h7, 28)
+ store<i32>(h_ptr, h8, 32)
+ store<i32>(h_ptr, h9, 36)
+}
+
+/**
+ * Square a FieldElement and store the result.
+ *
+ * Some iterations of the inner loop can be skipped since
+ * \`f[x] * f[y] + f[y] * f[x] = 2 * f[x] * f[y]\`
+ *
+ * \`\`\`
+ * // non-constant-time example
+ * for (let i = 0; i < 10; i++) {
+ * for (let j = i; j < 10; j++) {
+ * h[i + j] += f[i] * g[j]
+ * if (i < j) {
+ * h[i + j] *= 2
+ * }
+ * }
+ * }
+ * \`\`\`
+ *
+ * h = f * f
+ * Can overlap h with f.
+ *
+ * Preconditions:
+ * |f| bounded by 1.65x2²⁶,1.65x2²⁵,1.65x2²⁶,1.65x2²⁵,etc.
+ *
+ * Postconditions:
+ * |h| bounded by 1.01x2²⁵,1.01x2²⁴,1.01x2²⁵,1.01x2²⁴,etc.
+ *
+ * @param hx FieldElement power destination
+ * @param fx FieldElement base source
+ */
+export function fe_sq_vec (hx: FieldElement, fx: FieldElement, hy: FieldElement, fy: FieldElement): void {
+ const fx_ptr: usize = changetype<usize>(fx)
+ const fy_ptr: usize = changetype<usize>(fy)
+ const f0: v128 = i32x4(load<i32>(fx_ptr, 0), load<i32>(fy_ptr, 0), 0, 0)
+ const f1: v128 = i32x4(load<i32>(fx_ptr, 4), load<i32>(fy_ptr, 4), 0, 0)
+ const f2: v128 = i32x4(load<i32>(fx_ptr, 8), load<i32>(fy_ptr, 8), 0, 0)
+ const f3: v128 = i32x4(load<i32>(fx_ptr, 12), load<i32>(fy_ptr, 12), 0, 0)
+ const f4: v128 = i32x4(load<i32>(fx_ptr, 16), load<i32>(fy_ptr, 16), 0, 0)
+ const f5: v128 = i32x4(load<i32>(fx_ptr, 20), load<i32>(fy_ptr, 20), 0, 0)
+ const f6: v128 = i32x4(load<i32>(fx_ptr, 24), load<i32>(fy_ptr, 24), 0, 0)
+ const f7: v128 = i32x4(load<i32>(fx_ptr, 28), load<i32>(fy_ptr, 28), 0, 0)
+ const f8: v128 = i32x4(load<i32>(fx_ptr, 32), load<i32>(fy_ptr, 32), 0, 0)
+ const f9: v128 = i32x4(load<i32>(fx_ptr, 36), load<i32>(fy_ptr, 36), 0, 0)
+
+ const v19: v128 = i32x4.splat(19)
+ const f5_19: v128 = i32x4.mul(f5, v19) /* 1.959375*2²⁹ */
+ const f6_19: v128 = i32x4.mul(f6, v19) /* 1.959375*2³⁰ */
+ const f7_19: v128 = i32x4.mul(f7, v19) /* 1.959375*2²⁹ */
+ const f8_19: v128 = i32x4.mul(f8, v19) /* 1.959375*2³⁰ */
+ const f9_19: v128 = i32x4.mul(f9, v19) /* 1.959375*2²⁹ */
+
+ // f[0]
+ let f_2: v128 = i32x4.shl(f0, 1)
+
+ let h0: v128 = i64x2.extmul_low_i32x4_s(f0, f0)
+ let h1: v128 = i64x2.extmul_low_i32x4_s(f_2, f1)
+ let h2: v128 = i64x2.extmul_low_i32x4_s(f_2, f2)
+ let h3: v128 = i64x2.extmul_low_i32x4_s(f_2, f3)
+ let h4: v128 = i64x2.extmul_low_i32x4_s(f_2, f4)
+ let h5: v128 = i64x2.extmul_low_i32x4_s(f_2, f5)
+ let h6: v128 = i64x2.extmul_low_i32x4_s(f_2, f6)
+ let h7: v128 = i64x2.extmul_low_i32x4_s(f_2, f7)
+ let h8: v128 = i64x2.extmul_low_i32x4_s(f_2, f8)
+ let h9: v128 = i64x2.extmul_low_i32x4_s(f_2, f9)
+
+ // f[1]
+ f_2 = i32x4.shl(f1, 1)
+ let f_4: v128 = i32x4.shl(f1, 2)
+
+ let t0: v128 = i64x2.extmul_low_i32x4_s(f_2, f1)
+ let t1: v128 = i64x2.extmul_low_i32x4_s(f_2, f2)
+ let t2: v128 = i64x2.extmul_low_i32x4_s(f_4, f3)
+ let t3: v128 = i64x2.extmul_low_i32x4_s(f_2, f4)
+ let t4: v128 = i64x2.extmul_low_i32x4_s(f_4, f5)
+ let t5: v128 = i64x2.extmul_low_i32x4_s(f_2, f6)
+ let t6: v128 = i64x2.extmul_low_i32x4_s(f_4, f7)
+ let t7: v128 = i64x2.extmul_low_i32x4_s(f_2, f8)
+ let t8: v128 = i64x2.extmul_low_i32x4_s(f_4, f9_19)
+
+ h2 = i64x2.add(h2, t0)
+ h3 = i64x2.add(h3, t1)
+ h4 = i64x2.add(h4, t2)
+ h5 = i64x2.add(h5, t3)
+ h6 = i64x2.add(h6, t4)
+ h7 = i64x2.add(h7, t5)
+ h8 = i64x2.add(h8, t6)
+ h9 = i64x2.add(h9, t7)
+ h0 = i64x2.add(h0, t8)
+
+ // f[2]
+ f_2 = i32x4.shl(f2, 1)
+
+ t0 = i64x2.extmul_low_i32x4_s(f2, f2)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f3)
+ t2 = i64x2.extmul_low_i32x4_s(f_2, f4)
+ t3 = i64x2.extmul_low_i32x4_s(f_2, f5)
+ t4 = i64x2.extmul_low_i32x4_s(f_2, f6)
+ t5 = i64x2.extmul_low_i32x4_s(f_2, f7)
+ t6 = i64x2.extmul_low_i32x4_s(f_2, f8_19)
+ t7 = i64x2.extmul_low_i32x4_s(f_2, f9_19)
+
+ h4 = i64x2.add(h4, t0)
+ h5 = i64x2.add(h5, t1)
+ h6 = i64x2.add(h6, t2)
+ h7 = i64x2.add(h7, t3)
+ h8 = i64x2.add(h8, t4)
+ h9 = i64x2.add(h9, t5)
+ h0 = i64x2.add(h0, t6)
+ h1 = i64x2.add(h1, t7)
+
+ // f[3]
+ f_2 = i32x4.shl(f3, 1)
+ f_4 = i32x4.shl(f3, 2)
+
+ t0 = i64x2.extmul_low_i32x4_s(f_2, f3)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f4)
+ t2 = i64x2.extmul_low_i32x4_s(f_4, f5)
+ t3 = i64x2.extmul_low_i32x4_s(f_2, f6)
+ t4 = i64x2.extmul_low_i32x4_s(f_4, f7_19) /* 1.959375*2^30 */
+ t5 = i64x2.extmul_low_i32x4_s(f_2, f8_19)
+ t6 = i64x2.extmul_low_i32x4_s(f_4, f9_19)
+
+ h6 = i64x2.add(h6, t0)
+ h7 = i64x2.add(h7, t1)
+ h8 = i64x2.add(h8, t2)
+ h9 = i64x2.add(h9, t3)
+ h0 = i64x2.add(h0, t4) /* 1.959375*2^30 */
+ h1 = i64x2.add(h1, t5)
+ h2 = i64x2.add(h2, t6)
+
+ // f[4]
+ f_2 = i32x4.shl(f4, 1)
+
+ t0 = i64x2.extmul_low_i32x4_s(f4, f4)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f5)
+ t2 = i64x2.extmul_low_i32x4_s(f_2, f6_19)
+ t3 = i64x2.extmul_low_i32x4_s(f_2, f7_19)
+ t4 = i64x2.extmul_low_i32x4_s(f_2, f8_19)
+ t5 = i64x2.extmul_low_i32x4_s(f_2, f9_19)
+
+ h8 = i64x2.add(h8, t0)
+ h9 = i64x2.add(h9, t1)
+ h0 = i64x2.add(h0, t2)
+ h1 = i64x2.add(h1, t3)
+ h2 = i64x2.add(h2, t4)
+ h3 = i64x2.add(h3, t5)
+
+ // f[5]
+ f_2 = i32x4.shl(f5, 1)
+ f_4 = i32x4.shl(f5, 2)
+
+ t0 = i64x2.extmul_low_i32x4_s(f_2, f5_19)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f6_19)
+ t2 = i64x2.extmul_low_i32x4_s(f_4, f7_19)
+ t3 = i64x2.extmul_low_i32x4_s(f_2, f8_19)
+ t4 = i64x2.extmul_low_i32x4_s(f_4, f9_19) /* 1.959375*2^30 */
+
+ h0 = i64x2.add(h0, t0)
+ h1 = i64x2.add(h1, t1)
+ h2 = i64x2.add(h2, t2)
+ h3 = i64x2.add(h3, t3)
+ h4 = i64x2.add(h4, t4) /* 1.959375*2^30 */
+
+ // f[6]
+ f_2 = i32x4.shl(f6, 1)
+
+ t0 = i64x2.extmul_low_i32x4_s(f6, f6_19)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f7_19)
+ t2 = i64x2.extmul_low_i32x4_s(f_2, f8_19)
+ t3 = i64x2.extmul_low_i32x4_s(f_2, f9_19)
+
+ h2 = i64x2.add(h2, t0)
+ h3 = i64x2.add(h3, t1)
+ h4 = i64x2.add(h4, t2)
+ h5 = i64x2.add(h5, t3)
+
+ // f[7]
+ f_2 = i32x4.shl(f7, 1)
+ f_4 = i32x4.shl(f7, 2)
+
+ t0 = i64x2.extmul_low_i32x4_s(f_2, f7_19)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f8_19)
+ t2 = i64x2.extmul_low_i32x4_s(f_4, f9_19)
+
+ h4 = i64x2.add(h4, t0)
+ h5 = i64x2.add(h5, t1)
+ h6 = i64x2.add(h6, t2)
+
+ // f[8]
+ f_2 = i32x4.shl(f8, 1)
+
+ t0 = i64x2.extmul_low_i32x4_s(f8, f8_19)
+ t1 = i64x2.extmul_low_i32x4_s(f_2, f9_19)
+
+ h6 = i64x2.add(h6, t0)
+ h7 = i64x2.add(h7, t1)
+
+ // f[9]
+ f_2 = i32x4.shl(f9, 1)
+
+ t0 = i64x2.extmul_low_i32x4_s(f_2, f9_19)
+
+ h8 = i64x2.add(h8, t0)
+
+ const v24: v128 = i64x2.splat(1 << 24)
+ const v25: v128 = i64x2.splat(1 << 25)
+ let carry0: v128
+ let carry1: v128
+ let carry2: v128
+ let carry3: v128
+ let carry4: v128
+ let carry5: v128
+ let carry6: v128
+ let carry7: v128
+ let carry8: v128
+ let carry9: v128
+
+ carry0 = i64x2.shr_s(((i64x2.add(h0, v25))), 26)
+ h1 = i64x2.add(h1, carry0)
+ h0 = i64x2.sub(h0, i64x2.shl(carry0, 26))
+ carry4 = i64x2.shr_s(((i64x2.add(h4, v25))), 26)
+ h5 = i64x2.add(h5, carry4)
+ h4 = i64x2.sub(h4, i64x2.shl(carry4, 26))
+
+ carry1 = i64x2.shr_s(((i64x2.add(h1, v24))), 25)
+ h2 = i64x2.add(h2, carry1)
+ h1 = i64x2.sub(h1, i64x2.shl(carry1, 25))
+ carry5 = i64x2.shr_s(((i64x2.add(h5, v24))), 25)
+ h6 = i64x2.add(h6, carry5)
+ h5 = i64x2.sub(h5, i64x2.shl(carry5, 25))
+
+ carry2 = i64x2.shr_s(((i64x2.add(h2, v25))), 26)
+ h3 = i64x2.add(h3, carry2)
+ h2 = i64x2.sub(h2, i64x2.shl(carry2, 26))
+ carry6 = i64x2.shr_s(((i64x2.add(h6, v25))), 26)
+ h7 = i64x2.add(h7, carry6)
+ h6 = i64x2.sub(h6, i64x2.shl(carry6, 26))
+
+ carry3 = i64x2.shr_s(((i64x2.add(h3, v24))), 25)
+ h4 = i64x2.add(h4, carry3)
+ h3 = i64x2.sub(h3, i64x2.shl(carry3, 25))
+ carry7 = i64x2.shr_s(((i64x2.add(h7, v24))), 25)
+ h8 = i64x2.add(h8, carry7)
+ h7 = i64x2.sub(h7, i64x2.shl(carry7, 25))
+
+ carry4 = i64x2.shr_s(((i64x2.add(h4, v25))), 26)
+ h5 = i64x2.add(h5, carry4)
+ h4 = i64x2.sub(h4, i64x2.shl(carry4, 26))
+ carry8 = i64x2.shr_s(((i64x2.add(h8, v25))), 26)
+ h9 = i64x2.add(h9, carry8)
+ h8 = i64x2.sub(h8, i64x2.shl(carry8, 26))
+
+ carry9 = i64x2.shr_s(((i64x2.add(h9, v24))), 25)
+ h0 = i64x2.add(h0, i64x2.mul(carry9, i64x2.splat(19)))
+ h9 = i64x2.sub(h9, i64x2.shl(carry9, 25))
+
+ carry0 = i64x2.shr_s(((i64x2.add(h0, v25))), 26)
+ h1 = i64x2.add(h1, carry0)
+ h0 = i64x2.sub(h0, i64x2.shl(carry0, 26))
+
+ const hx_ptr: usize = changetype<usize>(hx)
+ store<i32>(hx_ptr, i64x2.extract_lane(h0, 0), 0)
+ store<i32>(hx_ptr, i64x2.extract_lane(h1, 0), 4)
+ store<i32>(hx_ptr, i64x2.extract_lane(h2, 0), 8)
+ store<i32>(hx_ptr, i64x2.extract_lane(h3, 0), 12)
+ store<i32>(hx_ptr, i64x2.extract_lane(h4, 0), 16)
+ store<i32>(hx_ptr, i64x2.extract_lane(h5, 0), 20)
+ store<i32>(hx_ptr, i64x2.extract_lane(h6, 0), 24)
+ store<i32>(hx_ptr, i64x2.extract_lane(h7, 0), 28)
+ store<i32>(hx_ptr, i64x2.extract_lane(h8, 0), 32)
+ store<i32>(hx_ptr, i64x2.extract_lane(h9, 0), 36)
+
+ const hy_ptr: usize = changetype<usize>(hy)
+ store<i32>(hy_ptr, i64x2.extract_lane(h0, 1), 0)
+ store<i32>(hy_ptr, i64x2.extract_lane(h1, 1), 4)
+ store<i32>(hy_ptr, i64x2.extract_lane(h2, 1), 8)
+ store<i32>(hy_ptr, i64x2.extract_lane(h3, 1), 12)
+ store<i32>(hy_ptr, i64x2.extract_lane(h4, 1), 16)
+ store<i32>(hy_ptr, i64x2.extract_lane(h5, 1), 20)
+ store<i32>(hy_ptr, i64x2.extract_lane(h6, 1), 24)
+ store<i32>(hy_ptr, i64x2.extract_lane(h7, 1), 28)
+ store<i32>(hy_ptr, i64x2.extract_lane(h8, 1), 32)
+ store<i32>(hy_ptr, i64x2.extract_lane(h9, 1), 36)
+}
+
+/**
+ * Square a FieldElement, double it, and store the result.
+ *
+ * h = 2 * f * f
+ * Can overlap h with f.
+ *
+ * Preconditions:
+ * |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+ *
+ * Postconditions:
+ * |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+ *
+ * @param h FieldElement power destination
+ * @param f FieldElement base source
+ */
+export function fe_sq2 (h: FieldElement, f: FieldElement): void {
+ const f_ptr: usize = changetype<usize>(f)
+ const f0: i64 = i64(load<i32>(f_ptr, 0))
+ const f1: i64 = i64(load<i32>(f_ptr, 4))
+ const f2: i64 = i64(load<i32>(f_ptr, 8))
+ const f3: i64 = i64(load<i32>(f_ptr, 12))
+ const f4: i64 = i64(load<i32>(f_ptr, 16))
+ const f5: i64 = i64(load<i32>(f_ptr, 20))
+ const f6: i64 = i64(load<i32>(f_ptr, 24))
+ const f7: i64 = i64(load<i32>(f_ptr, 28))
+ const f8: i64 = i64(load<i32>(f_ptr, 32))
+ const f9: i64 = i64(load<i32>(f_ptr, 36))
+
+ const f0_2: i64 = f0 * 2
+ const f1_2: i64 = f1 * 2
+ const f2_2: i64 = f2 * 2
+ const f3_2: i64 = f3 * 2
+ const f4_2: i64 = f4 * 2
+ const f5_2: i64 = f5 * 2
+ const f6_2: i64 = f6 * 2
+ const f7_2: i64 = f7 * 2
+
+ const f5_38: i64 = f5 * 38 /* 1.959375*2^30 */
+ const f6_19: i64 = f6 * 19 /* 1.959375*2^30 */
+ const f7_38: i64 = f7 * 38/* 1.959375*2^30 */
+ const f8_19: i64 = f8 * 19/* 1.959375*2^30 */
+ const f9_38: i64 = f9 * 38/* 1.959375*2^30 */
+
+ const f0f0: i64 = f0 * f0
+ const f0f1_2: i64 = f0_2 * f1
+ const f0f2_2: i64 = f0_2 * f2
+ const f0f3_2: i64 = f0_2 * f3
+ const f0f4_2: i64 = f0_2 * f4
+ const f0f5_2: i64 = f0_2 * f5
+ const f0f6_2: i64 = f0_2 * f6
+ const f0f7_2: i64 = f0_2 * f7
+ const f0f8_2: i64 = f0_2 * f8
+ const f0f9_2: i64 = f0_2 * f9
+
+ const f1f1_2: i64 = f1_2 * f1
+ const f1f2_2: i64 = f1_2 * f2
+ const f1f3_4: i64 = f1_2 * f3_2
+ const f1f4_2: i64 = f1_2 * f4
+ const f1f5_4: i64 = f1_2 * f5_2
+ const f1f6_2: i64 = f1_2 * f6
+ const f1f7_4: i64 = f1_2 * f7_2
+ const f1f8_2: i64 = f1_2 * f8
+ const f1f9_76: i64 = f1_2 * f9_38
+
+ const f2f2: i64 = f2 * f2
+ const f2f3_2: i64 = f2_2 * f3
+ const f2f4_2: i64 = f2_2 * f4
+ const f2f5_2: i64 = f2_2 * f5
+ const f2f6_2: i64 = f2_2 * f6
+ const f2f7_2: i64 = f2_2 * f7
+ const f2f8_38: i64 = f2_2 * f8_19
+ const f2f9_38: i64 = f2 * f9_38
+
+ const f3f3_2: i64 = f3_2 * f3
+ const f3f4_2: i64 = f3_2 * f4
+ const f3f5_4: i64 = f3_2 * f5_2
+ const f3f6_2: i64 = f3_2 * f6
+ const f3f7_76: i64 = f3_2 * f7_38
+ const f3f8_38: i64 = f3_2 * f8_19
+ const f3f9_76: i64 = f3_2 * f9_38
+
+ const f4f4: i64 = f4 * f4
+ const f4f5_2: i64 = f4_2 * f5
+ const f4f6_38: i64 = f4_2 * f6_19
+ const f4f7_38: i64 = f4 * f7_38
+ const f4f8_38: i64 = f4_2 * f8_19
+ const f4f9_38: i64 = f4 * f9_38
+
+ const f5f5_38: i64 = f5 * f5_38
+ const f5f6_38: i64 = f5_2 * f6_19
+ const f5f7_76: i64 = f5_2 * f7_38
+ const f5f8_38: i64 = f5_2 * f8_19
+ const f5f9_76: i64 = f5_2 * f9_38
+
+ const f6f6_19: i64 = f6 * f6_19
+ const f6f7_38: i64 = f6 * f7_38
+ const f6f8_38: i64 = f6_2 * f8_19
+ const f6f9_38: i64 = f6 * f9_38
+
+ const f7f7_38: i64 = f7 * f7_38
+ const f7f8_38: i64 = f7_2 * f8_19
+ const f7f9_76: i64 = f7_2 * f9_38
+
+ const f8f8_19: i64 = f8 * f8_19
+ const f8f9_38: i64 = f8 * f9_38
+
+ const f9f9_38: i64 = f9 * f9_38
+
+ let h0: i64 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
+ let h1: i64 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
+ let h2: i64 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
+ let h3: i64 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38
+ let h4: i64 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38
+ let h5: i64 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38
+ let h6: i64 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19
+ let h7: i64 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38
+ let h8: i64 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38
+ let h9: i64 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2
+
+ h0 *= 2
+ h1 *= 2
+ h2 *= 2
+ h3 *= 2
+ h4 *= 2
+ h5 *= 2
+ h6 *= 2
+ h7 *= 2
+ h8 *= 2
+ h9 *= 2
+
+ let carry0: i64
+ let carry1: i64
+ let carry2: i64
+ let carry3: i64
+ let carry4: i64
+ let carry5: i64
+ let carry6: i64
+ let carry7: i64
+ let carry8: i64
+ let carry9: i64
+
+ carry0 = (h0 + i64(1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+ carry4 = (h4 + i64(1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+
+ carry1 = (h1 + i64(1 << 24)) >> 25
+ h2 += carry1
+ h1 -= carry1 << 25
+ carry5 = (h5 + i64(1 << 24)) >> 25
+ h6 += carry5
+ h5 -= carry5 << 25
+
+ carry2 = (h2 + i64(1 << 25)) >> 26
+ h3 += carry2
+ h2 -= carry2 << 26
+ carry6 = (h6 + i64(1 << 25)) >> 26
+ h7 += carry6
+ h6 -= carry6 << 26
+
+ carry3 = (h3 + i64(1 << 24)) >> 25
+ h4 += carry3
+ h3 -= carry3 << 25
+ carry7 = (h7 + i64(1 << 24)) >> 25
+ h8 += carry7
+ h7 -= carry7 << 25
+
+ carry4 = (h4 + i64(1 << 25)) >> 26
+ h5 += carry4
+ h4 -= carry4 << 26
+ carry8 = (h8 + i64(1 << 25)) >> 26
+ h9 += carry8
+ h8 -= carry8 << 26
+
+ carry9 = (h9 + i64(1 << 24)) >> 25
+ h0 += carry9 * 19
+ h9 -= carry9 << 25
+
+ carry0 = (h0 + i64(1 << 25)) >> 26
+ h1 += carry0
+ h0 -= carry0 << 26
+
+ const h_ptr: usize = changetype<usize>(h)
+ store<i32>(h_ptr, h0, 0)
+ store<i32>(h_ptr, h1, 4)
+ store<i32>(h_ptr, h2, 8)
+ store<i32>(h_ptr, h3, 12)
+ store<i32>(h_ptr, h4, 16)
+ store<i32>(h_ptr, h5, 20)
+ store<i32>(h_ptr, h6, 24)
+ store<i32>(h_ptr, h7, 28)
+ store<i32>(h_ptr, h8, 32)
+ store<i32>(h_ptr, h9, 36)
+}
+
+/**
+ * Subtract a FieldElement another and store the result.
+ *
+ * @param h FieldElement difference destination
+ * @param f FieldElement minuend source
+ * @param g FieldElement subtrahend source
+ */
+//@ts-expect-error
+@inline
+export function fe_sub (h: FieldElement, f: FieldElement, g: FieldElement): void {
+ const f_ptr = changetype<usize>(f)
+ const g_ptr = changetype<usize>(g)
+ const h_ptr = changetype<usize>(h)
+ v128.store(h_ptr, v128.sub<i32>(v128.load(f_ptr, 0), v128.load(g_ptr, 0)), 0)
+ v128.store(h_ptr, v128.sub<i32>(v128.load(f_ptr, 16), v128.load(g_ptr, 16)), 16)
+ v128.store_lane<i64>(h_ptr, v128.sub<i32>(v128.load(f_ptr, 32), v128.load(g_ptr, 32)), 0, 32)
+}
+
+const fe_tobytes_t: FieldElement = fe()
+/**
+ * Goal: Output h0+...+2²⁵⁵ h10-2²⁵⁵ q, which is between 0 and 2²⁵⁵-20.
+ * Have h0+...+2²³⁰ h9 between 0 and 2²⁵⁵-1;
+ * evidently 2²⁵⁵ h10-2²⁵⁵ q = 0.
+ *
+ * Goal: Output h0+...+2²³⁰ h9.
+ */
+export function fe_tobytes (s: StaticArray<u8>, h: FieldElement): void {
+ const t = fe_tobytes_t
+ fe_reduce(t, h)
+ const t_ptr: usize = changetype<usize>(t)
+ const t0 = load<i32>(t_ptr, 0)
+ const t1 = load<i32>(t_ptr, 4)
+ const t2 = load<i32>(t_ptr, 8)
+ const t3 = load<i32>(t_ptr, 12)
+ const t4 = load<i32>(t_ptr, 16)
+ const t5 = load<i32>(t_ptr, 20)
+ const t6 = load<i32>(t_ptr, 24)
+ const t7 = load<i32>(t_ptr, 28)
+ const t8 = load<i32>(t_ptr, 32)
+ const t9 = load<i32>(t_ptr, 36)
+
+ const s_ptr: usize = changetype<usize>(s)
+ store<u8>(s_ptr, t0 >> 0, 0)
+ store<u8>(s_ptr, t0 >> 8, 1)
+ store<u8>(s_ptr, t0 >> 16, 2)
+ store<u8>(s_ptr, (t0 >> 24) | (t1 << 2), 3)
+ store<u8>(s_ptr, t1 >> 6, 4)
+ store<u8>(s_ptr, t1 >> 14, 5)
+ store<u8>(s_ptr, (t1 >> 22) | (t2 << 3), 6)
+ store<u8>(s_ptr, t2 >> 5, 7)
+ store<u8>(s_ptr, t2 >> 13, 8)
+ store<u8>(s_ptr, (t2 >> 21) | (t3 << 5), 9)
+ store<u8>(s_ptr, t3 >> 3, 10)
+ store<u8>(s_ptr, t3 >> 11, 11)
+ store<u8>(s_ptr, (t3 >> 19) | (t4 << 6), 12)
+ store<u8>(s_ptr, t4 >> 2, 13)
+ store<u8>(s_ptr, t4 >> 10, 14)
+ store<u8>(s_ptr, t4 >> 18, 15)
+ store<u8>(s_ptr, t5 >> 0, 16)
+ store<u8>(s_ptr, t5 >> 8, 17)
+ store<u8>(s_ptr, t5 >> 16, 18)
+ store<u8>(s_ptr, (t5 >> 24) | (t6 << 1), 19)
+ store<u8>(s_ptr, t6 >> 7, 20)
+ store<u8>(s_ptr, t6 >> 15, 21)
+ store<u8>(s_ptr, (t6 >> 23) | (t7 << 3), 22)
+ store<u8>(s_ptr, t7 >> 5, 23)
+ store<u8>(s_ptr, t7 >> 13, 24)
+ store<u8>(s_ptr, (t7 >> 21) | (t8 << 4), 25)
+ store<u8>(s_ptr, t8 >> 4, 26)
+ store<u8>(s_ptr, t8 >> 12, 27)
+ store<u8>(s_ptr, (t8 >> 20) | (t9 << 6), 28)
+ store<u8>(s_ptr, t9 >> 2, 29)
+ store<u8>(s_ptr, t9 >> 10, 30)
+ store<u8>(s_ptr, t9 >> 18, 31)
+}
+`
+
+await writeFile('src/assembly/ed25519/fe.ts', FE)