]> git.codecow.com Git - nano25519.git/commitdiff
Start moving to generator script instead of unrolling and inlining by hand.
authorChris Duncan <chris@zoso.dev>
Wed, 30 Sep 2026 04:31:28 +0000 (21:31 -0700)
committerChris Duncan <chris@zoso.dev>
Wed, 30 Sep 2026 04:31:28 +0000 (21:31 -0700)
package.json
scripts/blake2b.gen.mjs [moved from scripts/blake2b-gen.mjs with 100% similarity]
scripts/fe.gen.mjs [new file with mode: 0644]

index 9ec3d1c6e9af53972a236e03bb0abe3e49c54802..fa9b4fb7f231de5a3c72f5bfa7fd38d739f4390e 100644 (file)
                "build": "npm run clean && npm run compile && node ./esbuild/dev.mjs",
                "build:prod": "npm run clean && npm run compile && node ./esbuild/prod.mjs",
                "clean": "rm -rf {build,dist}",
-               "compile": "node scripts/blake2b-gen.mjs && asc ./src/assembly/index.ts && tsc",
+               "compile": "npm run generate && asc ./src/assembly/index.ts && tsc",
+               "generate": "npm run generate:blake2b & npm run generate:fe",
+               "generate:blake2b": "node scripts/blake2b.gen.mjs",
+               "generate:fe": "node scripts/fe.gen.mjs",
                "prepublishOnly": "npm run test:prod",
                "test": "npm run build && npm run test:all",
                "test:all": "npm run test:le && npm run test:be",
diff --git a/scripts/fe.gen.mjs b/scripts/fe.gen.mjs
new file mode 100644 (file)
index 0000000..ace542d
--- /dev/null
@@ -0,0 +1,1476 @@
+//! 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)