From: Chris Duncan Date: Wed, 30 Sep 2026 04:31:28 +0000 (-0700) Subject: Start moving to generator script instead of unrolling and inlining by hand. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=3736152531b506fd3523325c25dd423cd9472d46;p=nano25519.git Start moving to generator script instead of unrolling and inlining by hand. --- diff --git a/package.json b/package.json index 9ec3d1c..fa9b4fb 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,10 @@ "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/blake2b-gen.mjs b/scripts/blake2b.gen.mjs similarity index 100% rename from scripts/blake2b-gen.mjs rename to scripts/blake2b.gen.mjs diff --git a/scripts/fe.gen.mjs b/scripts/fe.gen.mjs new file mode 100644 index 0000000..ace542d --- /dev/null +++ b/scripts/fe.gen.mjs @@ -0,0 +1,1476 @@ +//! SPDX-FileCopyrightText: 2026 Chris Duncan +//! 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 +//! 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 +export function fe (): FieldElement { + return new StaticArray(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(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(changetype(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(h) + const f_ptr = changetype(f) + const g_ptr = changetype(g) + v128.store(h_ptr, v128.add(v128.load(f_ptr, 0), v128.load(g_ptr, 0)), 0) + v128.store(h_ptr, v128.add(v128.load(f_ptr, 16), v128.load(g_ptr, 16)), 16) + v128.store_lane(h_ptr, v128.add(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(f) + const g_ptr = changetype(g) + const c = v128.splat(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(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(h), changetype(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(h) + const f_ptr = changetype(f) + v128.store(h_ptr, v128.shl(v128.load(f_ptr, 0), 1), 0) + v128.store(h_ptr, v128.shl(v128.load(f_ptr, 16), 1), 16) + v128.store_lane(h_ptr, v128.shl(v128.load(f_ptr, 32), 1), 0, 32) +} + +/** + * Ignores top bit of s. + */ +export function fe_frombytes (h: FieldElement, s: StaticArray): 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(h) + store(h_ptr, h0, 0) + store(h_ptr, h1, 4) + store(h_ptr, h2, 8) + store(h_ptr, h3, 12) + store(h_ptr, h4, 16) + store(h_ptr, h5, 20) + store(h_ptr, h6, 24) + store(h_ptr, h7, 28) + store(h_ptr, h8, 32) + store(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(changetype(t), 0) & 1 +} + +const fe_iszero_s = new StaticArray(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(f) + const g_ptr: usize = changetype(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(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(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(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(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(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(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(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(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(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(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(h) + store(h_ptr, h0, 0) + store(h_ptr, h1, 4) + store(h_ptr, h2, 8) + store(h_ptr, h3, 12) + store(h_ptr, h4, 16) + store(h_ptr, h5, 20) + store(h_ptr, h6, 24) + store(h_ptr, h7, 28) + store(h_ptr, h8, 32) + store(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(h) + const f_ptr = changetype(f) + v128.store(h_ptr, v128.neg(v128.load(f_ptr))) + v128.store(h_ptr, v128.neg(v128.load(f_ptr, 16)), 16) + v128.store_lane(h_ptr, v128.neg(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(f) + let f0: i32 = load(f_ptr, 0) + let f1: i32 = load(f_ptr, 4) + let f2: i32 = load(f_ptr, 8) + let f3: i32 = load(f_ptr, 12) + let f4: i32 = load(f_ptr, 16) + let f5: i32 = load(f_ptr, 20) + let f6: i32 = load(f_ptr, 24) + let f7: i32 = load(f_ptr, 28) + let f8: i32 = load(f_ptr, 32) + let f9: i32 = load(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(h) + store(h_ptr, f0, 0) + store(h_ptr, f1, 4) + store(h_ptr, f2, 8) + store(h_ptr, f3, 12) + store(h_ptr, f4, 16) + store(h_ptr, f5, 20) + store(h_ptr, f6, 24) + store(h_ptr, f7, 28) + store(h_ptr, f8, 32) + store(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(f) + const f0: i64 = i64(load(f_ptr, 0)) + const f1: i64 = i64(load(f_ptr, 4)) + const f2: i64 = i64(load(f_ptr, 8)) + const f3: i64 = i64(load(f_ptr, 12)) + const f4: i64 = i64(load(f_ptr, 16)) + const f5: i64 = i64(load(f_ptr, 20)) + const f6: i64 = i64(load(f_ptr, 24)) + const f7: i64 = i64(load(f_ptr, 28)) + const f8: i64 = i64(load(f_ptr, 32)) + const f9: i64 = i64(load(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(h) + store(h_ptr, h0, 0) + store(h_ptr, h1, 4) + store(h_ptr, h2, 8) + store(h_ptr, h3, 12) + store(h_ptr, h4, 16) + store(h_ptr, h5, 20) + store(h_ptr, h6, 24) + store(h_ptr, h7, 28) + store(h_ptr, h8, 32) + store(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(fx) + const fy_ptr: usize = changetype(fy) + const f0: v128 = i32x4(load(fx_ptr, 0), load(fy_ptr, 0), 0, 0) + const f1: v128 = i32x4(load(fx_ptr, 4), load(fy_ptr, 4), 0, 0) + const f2: v128 = i32x4(load(fx_ptr, 8), load(fy_ptr, 8), 0, 0) + const f3: v128 = i32x4(load(fx_ptr, 12), load(fy_ptr, 12), 0, 0) + const f4: v128 = i32x4(load(fx_ptr, 16), load(fy_ptr, 16), 0, 0) + const f5: v128 = i32x4(load(fx_ptr, 20), load(fy_ptr, 20), 0, 0) + const f6: v128 = i32x4(load(fx_ptr, 24), load(fy_ptr, 24), 0, 0) + const f7: v128 = i32x4(load(fx_ptr, 28), load(fy_ptr, 28), 0, 0) + const f8: v128 = i32x4(load(fx_ptr, 32), load(fy_ptr, 32), 0, 0) + const f9: v128 = i32x4(load(fx_ptr, 36), load(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(hx) + store(hx_ptr, i64x2.extract_lane(h0, 0), 0) + store(hx_ptr, i64x2.extract_lane(h1, 0), 4) + store(hx_ptr, i64x2.extract_lane(h2, 0), 8) + store(hx_ptr, i64x2.extract_lane(h3, 0), 12) + store(hx_ptr, i64x2.extract_lane(h4, 0), 16) + store(hx_ptr, i64x2.extract_lane(h5, 0), 20) + store(hx_ptr, i64x2.extract_lane(h6, 0), 24) + store(hx_ptr, i64x2.extract_lane(h7, 0), 28) + store(hx_ptr, i64x2.extract_lane(h8, 0), 32) + store(hx_ptr, i64x2.extract_lane(h9, 0), 36) + + const hy_ptr: usize = changetype(hy) + store(hy_ptr, i64x2.extract_lane(h0, 1), 0) + store(hy_ptr, i64x2.extract_lane(h1, 1), 4) + store(hy_ptr, i64x2.extract_lane(h2, 1), 8) + store(hy_ptr, i64x2.extract_lane(h3, 1), 12) + store(hy_ptr, i64x2.extract_lane(h4, 1), 16) + store(hy_ptr, i64x2.extract_lane(h5, 1), 20) + store(hy_ptr, i64x2.extract_lane(h6, 1), 24) + store(hy_ptr, i64x2.extract_lane(h7, 1), 28) + store(hy_ptr, i64x2.extract_lane(h8, 1), 32) + store(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(f) + const f0: i64 = i64(load(f_ptr, 0)) + const f1: i64 = i64(load(f_ptr, 4)) + const f2: i64 = i64(load(f_ptr, 8)) + const f3: i64 = i64(load(f_ptr, 12)) + const f4: i64 = i64(load(f_ptr, 16)) + const f5: i64 = i64(load(f_ptr, 20)) + const f6: i64 = i64(load(f_ptr, 24)) + const f7: i64 = i64(load(f_ptr, 28)) + const f8: i64 = i64(load(f_ptr, 32)) + const f9: i64 = i64(load(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(h) + store(h_ptr, h0, 0) + store(h_ptr, h1, 4) + store(h_ptr, h2, 8) + store(h_ptr, h3, 12) + store(h_ptr, h4, 16) + store(h_ptr, h5, 20) + store(h_ptr, h6, 24) + store(h_ptr, h7, 28) + store(h_ptr, h8, 32) + store(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(f) + const g_ptr = changetype(g) + const h_ptr = changetype(h) + v128.store(h_ptr, v128.sub(v128.load(f_ptr, 0), v128.load(g_ptr, 0)), 0) + v128.store(h_ptr, v128.sub(v128.load(f_ptr, 16), v128.load(g_ptr, 16)), 16) + v128.store_lane(h_ptr, v128.sub(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, h: FieldElement): void { + const t = fe_tobytes_t + fe_reduce(t, h) + const t_ptr: usize = changetype(t) + const t0 = load(t_ptr, 0) + const t1 = load(t_ptr, 4) + const t2 = load(t_ptr, 8) + const t3 = load(t_ptr, 12) + const t4 = load(t_ptr, 16) + const t5 = load(t_ptr, 20) + const t6 = load(t_ptr, 24) + const t7 = load(t_ptr, 28) + const t8 = load(t_ptr, 32) + const t9 = load(t_ptr, 36) + + const s_ptr: usize = changetype(s) + store(s_ptr, t0 >> 0, 0) + store(s_ptr, t0 >> 8, 1) + store(s_ptr, t0 >> 16, 2) + store(s_ptr, (t0 >> 24) | (t1 << 2), 3) + store(s_ptr, t1 >> 6, 4) + store(s_ptr, t1 >> 14, 5) + store(s_ptr, (t1 >> 22) | (t2 << 3), 6) + store(s_ptr, t2 >> 5, 7) + store(s_ptr, t2 >> 13, 8) + store(s_ptr, (t2 >> 21) | (t3 << 5), 9) + store(s_ptr, t3 >> 3, 10) + store(s_ptr, t3 >> 11, 11) + store(s_ptr, (t3 >> 19) | (t4 << 6), 12) + store(s_ptr, t4 >> 2, 13) + store(s_ptr, t4 >> 10, 14) + store(s_ptr, t4 >> 18, 15) + store(s_ptr, t5 >> 0, 16) + store(s_ptr, t5 >> 8, 17) + store(s_ptr, t5 >> 16, 18) + store(s_ptr, (t5 >> 24) | (t6 << 1), 19) + store(s_ptr, t6 >> 7, 20) + store(s_ptr, t6 >> 15, 21) + store(s_ptr, (t6 >> 23) | (t7 << 3), 22) + store(s_ptr, t7 >> 5, 23) + store(s_ptr, t7 >> 13, 24) + store(s_ptr, (t7 >> 21) | (t8 << 4), 25) + store(s_ptr, t8 >> 4, 26) + store(s_ptr, t8 >> 12, 27) + store(s_ptr, (t8 >> 20) | (t9 << 6), 28) + store(s_ptr, t9 >> 2, 29) + store(s_ptr, t9 >> 10, 30) + store(s_ptr, t9 >> 18, 31) +} +` + +await writeFile('src/assembly/ed25519/fe.ts', FE)