]> git.codecow.com Git - nano25519.git/commitdiff
Enable p generator and field element subtraction.
authorChris Duncan <chris@codecow.com>
Wed, 30 Sep 2026 15:25:32 +0000 (08:25 -0700)
committerChris Duncan <chris@codecow.com>
Wed, 30 Sep 2026 15:25:32 +0000 (08:25 -0700)
scripts/fe.gen.mjs
scripts/index.mjs
scripts/p.gen.mjs
src/assembly/ed25519/fe.ts
src/assembly/ed25519/p.ts

index 358b52e93de0dcb20a78cff772cafa92ad082487..3f62a701012beea31ae886bf886a03d74239e7d3 100644 (file)
  */
 export function fe_add (h, f, g) {
        return `
+       // ${h} = ${f} + ${g}
        ${h}0 = v128.add<i32>(${f}0, ${g}0)
        ${h}1 = v128.add<i32>(${f}1, ${g}1)
        ${h}2 = v128.add<i32>(${f}2, ${g}2)
-       `
+`
+}
+
+/**
+ * Subtract a FieldElement another and store the result.
+ *
+ * @param {string} h Variable name of FieldElement difference destination
+ * @param {string} f Variable name of FieldElement minuend source
+ * @param {string} g Variable name of FieldElement subtrahend source
+ */
+export function fe_sub (h, f, g) {
+       return `
+       // ${h} = ${f} - ${g}
+       ${h}0 = v128.sub<i32>(${f}0, ${g}0)
+       ${h}1 = v128.sub<i32>(${f}1, ${g}1)
+       ${h}2 = v128.sub<i32>(${f}2, ${g}2)
+`
 }
 
 export const FE = `//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
@@ -1433,12 +1450,26 @@ export function fe_sq2 (h: FieldElement, f: FieldElement): void {
 //@ts-expect-error
 @inline
 export function fe_sub (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)
-       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)
+
+       let h0: v128, h1: v128, h2: v128
+
+       let f0 = v128.load(f_ptr, 0)
+       let f1 = v128.load(f_ptr, 16)
+       let f2 = v128.load(f_ptr, 32)
+
+       let g0 = v128.load(g_ptr, 0)
+       let g1 = v128.load(g_ptr, 16)
+       let g2 = v128.load(g_ptr, 32)
+
+       ${fe_sub('h', 'f', 'g')}
+
+       // store h
+       v128.store(h_ptr, h0, 0)
+       v128.store(h_ptr, h1, 16)
+       v128.store_lane<u64>(h_ptr, h2, 0, 32)
 }
 
 const fe_tobytes_t: FieldElement = fe()
index bdec654aaa2d2a95a006fbacd3e9be532ccac1ca..a264b9dd77d035361522d42cea71eb10c2c3aa18 100644 (file)
@@ -23,4 +23,4 @@ const stripNewline = s => s.replaceAll(/\n([\r\s]*\n){2,}/gm, '\n\n')
 await writeFile('src/assembly/ed25519/blake2b.ts', stripNewline(BLAKE2b))
 await writeFile('src/assembly/ed25519/fe.ts', stripNewline(FE))
 await writeFile('src/assembly/ed25519/ge.ts', stripNewline(GE))
-// await writeFile('src/assembly/ed25519/p.ts', stripNewline(P))
+await writeFile('src/assembly/ed25519/p.ts', stripNewline(P))
index d897f983bf69c6ad1ffc37e5b06202f3553a163e..04c63d802b952ce99506aac42e5f3c901513f336 100644 (file)
@@ -9,6 +9,8 @@
  * AssemblyScript compiler and type checker.
  */
 
+import { fe_add, fe_sub } from './fe.gen.mjs'
+
 export const P = `//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
@@ -62,15 +64,66 @@ const ge_p2_dbl_t0: FieldElement = fe()
 //@ts-expect-error
 @inline
 export function ge_p2_dbl (r: ge_p1p1, p: ge_p2): void {
-       const t0 = ge_p2_dbl_t0
-       fe_sq_vec(r.X, p.X, r.Z, p.Y)
-       fe_sq2(r.T, p.Z)
-       fe_add(r.Y, p.X, p.Y)
-       fe_sq(t0, r.Y)
-       fe_add(r.Y, r.Z, r.X)
-       fe_sub(r.Z, r.Z, r.X)
-       fe_sub(r.X, t0, r.Y)
-       fe_sub(r.T, r.T, r.Z)
+       const rX_ptr = changetype<usize>(r.X)
+       const rY_ptr = changetype<usize>(r.Y)
+       const rZ_ptr = changetype<usize>(r.Z)
+       const rT_ptr = changetype<usize>(r.T)
+
+       const pX_ptr = changetype<usize>(p.X)
+       const pY_ptr = changetype<usize>(p.Y)
+       const pZ_ptr = changetype<usize>(p.Z)
+
+       const rYsq = ge_p2_dbl_t0
+       const rYsq_ptr = changetype<usize>(rYsq)
+
+       fe_sq_vec(r.X, r.Z, p.X, p.Y) // rX = pX²; rY = pY²
+       fe_sq2(r.T, p.Z) // rT = pZ²
+
+       // Start converting to performant inlined ops with locals to avoid load/store
+       let rX0 = v128.load(rX_ptr), rX1 = v128.load(rX_ptr, 16), rX2 = v128.load(rX_ptr, 32)
+       let rY0 = v128.load(rY_ptr), rY1 = v128.load(rY_ptr, 16), rY2 = v128.load(rY_ptr, 32)
+       let rZ0 = v128.load(rZ_ptr), rZ1 = v128.load(rZ_ptr, 16), rZ2 = v128.load(rZ_ptr, 32)
+       let rT0 = v128.load(rT_ptr), rT1 = v128.load(rT_ptr, 16), rT2 = v128.load(rT_ptr, 32)
+
+       const pX0 = v128.load(pX_ptr), pX1 = v128.load(pX_ptr, 16), pX2 = v128.load(pX_ptr, 32)
+       const pY0 = v128.load(pY_ptr), pY1 = v128.load(pY_ptr, 16), pY2 = v128.load(pY_ptr, 32)
+       const pZ0 = v128.load(pZ_ptr), pZ1 = v128.load(pZ_ptr, 16), pZ2 = v128.load(pZ_ptr, 32)
+
+       // rY = pX + pY
+       v128.store(rY_ptr, v128.add<i32>(pX0, pY0))
+       v128.store(rY_ptr, v128.add<i32>(pX1, pY1), 16)
+       v128.store_lane<u64>(rY_ptr, v128.add<i32>(pX2, pY2), 0, 32)
+
+       // t0 = r.Y²
+       fe_sq(rYsq, r.Y)
+       const rYsq0 = v128.load(rYsq_ptr, 0)
+       const rYsq1 = v128.load(rYsq_ptr, 16)
+       const rYsq2 = v128.load(rYsq_ptr, 32)
+
+       ${fe_add('rY', 'rZ', 'rX')}
+       ${fe_sub('rZ', 'rZ', 'rX')}
+       ${fe_sub('rX', 'rYsq', 'rY')}
+       ${fe_sub('rT', 'rT', 'rZ')}
+
+       // store r.X
+       v128.store(rX_ptr, rX0)
+       v128.store(rX_ptr, rX1, 16)
+       v128.store_lane<u64>(rX_ptr, rX2, 0, 32)
+
+       // store r.Y
+       v128.store(rY_ptr, rY0)
+       v128.store(rY_ptr, rY1, 16)
+       v128.store_lane<u64>(rY_ptr, rY2, 0, 32)
+
+       // store r.Z
+       v128.store(rZ_ptr, rZ0)
+       v128.store(rZ_ptr, rZ1, 16)
+       v128.store_lane<u64>(rZ_ptr, rZ2, 0, 32)
+
+       // store r.T
+       v128.store(rT_ptr, rT0)
+       v128.store(rT_ptr, rT1, 16)
+       v128.store_lane<u64>(rT_ptr, rT2, 0, 32)
 }
 
 /**
@@ -165,43 +218,192 @@ export function ge_precomp_0 (h: ge_precomp): void {
        fe_0(h.xy2d)
 }
 
-const ge_add_cached_t0: FieldElement = fe()
 /**
  * r = p + q
  */
 export function ge_add_cached (r: ge_p1p1, p: ge_p3, q: ge_cached): void {
-       const t0 = ge_add_cached_t0
-       fe_add(r.X, p.Y, p.X)
-       fe_sub(r.Y, p.Y, p.X)
+       const rX_ptr = changetype<usize>(r.X)
+       const rY_ptr = changetype<usize>(r.Y)
+       const rZ_ptr = changetype<usize>(r.Z)
+       const rT_ptr = changetype<usize>(r.T)
+
+       const pX_ptr = changetype<usize>(p.X)
+       const pY_ptr = changetype<usize>(p.Y)
+
+       let pX0 = v128.load(pX_ptr), pX1 = v128.load(pX_ptr, 16), pX2 = v128.load(pX_ptr, 32)
+       let pY0 = v128.load(pY_ptr), pY1 = v128.load(pY_ptr, 16), pY2 = v128.load(pY_ptr, 32)
+
+       // fe_add(r.X, p.Y, p.X)
+       let rX0 = v128.add<i32>(pY0, pX0)
+       let rX1 = v128.add<i32>(pY1, pX1)
+       let rX2 = v128.add<i32>(pY2, pX2)
+
+       // fe_sub(r.Y, p.Y, p.X)
+       let rY0 = v128.sub<i32>(pY0, pX0)
+       let rY1 = v128.sub<i32>(pY1, pX1)
+       let rY2 = v128.sub<i32>(pY2, pX2)
+
+       // store r.X
+       v128.store(rX_ptr, rX0)
+       v128.store(rX_ptr, rX1, 16)
+       v128.store_lane<u64>(rX_ptr, rX2, 0, 32)
+
+       // store r.Y
+       v128.store(rY_ptr, rY0)
+       v128.store(rY_ptr, rY1, 16)
+       v128.store_lane<u64>(rY_ptr, rY2, 0, 32)
+
+       // not yet converted to inline for size and complexity
        fe_mul(r.Z, r.X, q.YplusX)
        fe_mul(r.Y, r.Y, q.YminusX)
        fe_mul(r.T, q.T2d, p.T)
        fe_mul(r.X, p.Z, q.Z)
-       fe_dbl(t0, r.X)
-       fe_sub(r.X, r.Z, r.Y)
-       fe_add(r.Y, r.Z, r.Y)
-       fe_add(r.Z, t0, r.T)
-       fe_sub(r.T, t0, r.T)
+
+       // remaining arithmetic done inline to avoid unnecessary load/store
+       rX0 = v128.load(rX_ptr), rX1 = v128.load(rX_ptr, 16), rX2 = v128.load(rX_ptr, 32)
+       rY0 = v128.load(rY_ptr), rY1 = v128.load(rY_ptr, 16), rY2 = v128.load(rY_ptr, 32)
+       let rZ0 = v128.load(rZ_ptr), rZ1 = v128.load(rZ_ptr, 16), rZ2 = v128.load(rZ_ptr, 32)
+       let rT0 = v128.load(rT_ptr), rT1 = v128.load(rT_ptr, 16), rT2 = v128.load(rT_ptr, 32)
+
+       // fe_dbl(t0, r.X)
+       const t0 = v128.shl<i32>(rX0, 1)
+       const t1 = v128.shl<i32>(rX1, 1)
+       const t2 = v128.shl<i32>(rX2, 1)
+
+       // fe_sub(r.X, r.Z, r.Y)
+       rX0 = v128.sub<i32>(rZ0, rY0)
+       rX1 = v128.sub<i32>(rZ1, rY1)
+       rX2 = v128.sub<i32>(rZ2, rY2)
+
+       // fe_add(r.Y, r.Z, r.Y)
+       rY0 = v128.add<i32>(rZ0, rY0)
+       rY1 = v128.add<i32>(rZ1, rY1)
+       rY2 = v128.add<i32>(rZ2, rY2)
+
+       // fe_add(r.Z, t, r.T)
+       rZ0 = v128.add<i32>(t0, rT0)
+       rZ1 = v128.add<i32>(t1, rT1)
+       rZ2 = v128.add<i32>(t2, rT2)
+
+       // fe_sub(r.T, t, r.T)
+       rT0 = v128.sub<i32>(t0, rT0)
+       rT1 = v128.sub<i32>(t1, rT1)
+       rT2 = v128.sub<i32>(t2, rT2)
+
+       // store r.X
+       v128.store(rX_ptr, rX0)
+       v128.store(rX_ptr, rX1, 16)
+       v128.store_lane<u64>(rX_ptr, rX2, 0, 32)
+
+       // store r.Y
+       v128.store(rY_ptr, rY0)
+       v128.store(rY_ptr, rY1, 16)
+       v128.store_lane<u64>(rY_ptr, rY2, 0, 32)
+
+       // store r.Z
+       v128.store(rZ_ptr, rZ0)
+       v128.store(rZ_ptr, rZ1, 16)
+       v128.store_lane<u64>(rZ_ptr, rZ2, 0, 32)
+
+       // store r.T
+       v128.store(rT_ptr, rT0)
+       v128.store(rT_ptr, rT1, 16)
+       v128.store_lane<u64>(rT_ptr, rT2, 0, 32)
 }
 
-const ge_add_precomp_t0: FieldElement = fe()
 /**
  * r = p + q
  */
 //@ts-expect-error
 @inline
 export function ge_add_precomp (r: ge_p1p1, p: ge_p3, q: ge_precomp): void {
-       const t0 = ge_add_precomp_t0
-       fe_add(r.X, p.Y, p.X)
-       fe_sub(r.Y, p.Y, p.X)
+       const rX_ptr = changetype<usize>(r.X)
+       const rY_ptr = changetype<usize>(r.Y)
+       const rZ_ptr = changetype<usize>(r.Z)
+       const rT_ptr = changetype<usize>(r.T)
+
+       const pX_ptr = changetype<usize>(p.X)
+       const pY_ptr = changetype<usize>(p.Y)
+       const pZ_ptr = changetype<usize>(p.Z)
+
+       const pX0 = v128.load(pX_ptr), pX1 = v128.load(pX_ptr, 16), pX2 = v128.load(pX_ptr, 32)
+       const pY0 = v128.load(pY_ptr), pY1 = v128.load(pY_ptr, 16), pY2 = v128.load(pY_ptr, 32)
+
+       // fe_add(r.X, p.Y, p.X)
+       let rX0 = v128.add<i32>(pY0, pX0)
+       let rX1 = v128.add<i32>(pY1, pX1)
+       let rX2 = v128.add<i32>(pY2, pX2)
+
+       // fe_sub(r.Y, p.Y, p.X)
+       let rY0 = v128.sub<i32>(pY0, pX0)
+       let rY1 = v128.sub<i32>(pY1, pX1)
+       let rY2 = v128.sub<i32>(pY2, pX2)
+
+       // store r.X
+       v128.store(rX_ptr, rX0)
+       v128.store(rX_ptr, rX1, 16)
+       v128.store_lane<u64>(rX_ptr, rX2, 0, 32)
+
+       // store r.Y
+       v128.store(rY_ptr, rY0)
+       v128.store(rY_ptr, rY1, 16)
+       v128.store_lane<u64>(rY_ptr, rY2, 0, 32)
+
+       // not yet converted to inline for size and complexity
        fe_mul(r.Z, r.X, q.yplusx)
        fe_mul(r.Y, r.Y, q.yminusx)
        fe_mul(r.T, q.xy2d, p.T)
-       fe_dbl(t0, p.Z)
-       fe_sub(r.X, r.Z, r.Y)
-       fe_add(r.Y, r.Z, r.Y)
-       fe_add(r.Z, t0, r.T)
-       fe_sub(r.T, t0, r.T)
+
+       // remaining arithmetic done inline to avoid unnecessary load/store
+       rX0 = v128.load(rX_ptr), rX1 = v128.load(rX_ptr, 16), rX2 = v128.load(rX_ptr, 32)
+       rY0 = v128.load(rY_ptr), rY1 = v128.load(rY_ptr, 16), rY2 = v128.load(rY_ptr, 32)
+       let rZ0 = v128.load(rZ_ptr), rZ1 = v128.load(rZ_ptr, 16), rZ2 = v128.load(rZ_ptr, 32)
+       let rT0 = v128.load(rT_ptr), rT1 = v128.load(rT_ptr, 16), rT2 = v128.load(rT_ptr, 32)
+
+       // fe_dbl(t, p.Z)
+       const pZ0 = v128.shl<i32>(v128.load(pZ_ptr), 1)
+       const pZ1 = v128.shl<i32>(v128.load(pZ_ptr, 16), 1)
+       const pZ2 = v128.shl<i32>(v128.load(pZ_ptr, 32), 1)
+
+       // fe_sub(r.X, r.Z, r.Y)
+       rX0 = v128.sub<i32>(rZ0, rY0)
+       rX1 = v128.sub<i32>(rZ1, rY1)
+       rX2 = v128.sub<i32>(rZ2, rY2)
+
+       // fe_add(r.Y, r.Z, r.Y)
+       rY0 = v128.add<i32>(rZ0, rY0)
+       rY1 = v128.add<i32>(rZ1, rY1)
+       rY2 = v128.add<i32>(rZ2, rY2)
+
+       // fe_add(r.Z, t, r.T)
+       rZ0 = v128.add<i32>(pZ0, rT0)
+       rZ1 = v128.add<i32>(pZ1, rT1)
+       rZ2 = v128.add<i32>(pZ2, rT2)
+
+       // fe_sub(r.T, t, r.T)
+       rT0 = v128.sub<i32>(pZ0, rT0)
+       rT1 = v128.sub<i32>(pZ1, rT1)
+       rT2 = v128.sub<i32>(pZ2, rT2)
+
+       // store r.X
+       v128.store(rX_ptr, rX0)
+       v128.store(rX_ptr, rX1, 16)
+       v128.store_lane<u64>(rX_ptr, rX2, 0, 32)
+
+       // store r.Y
+       v128.store(rY_ptr, rY0)
+       v128.store(rY_ptr, rY1, 16)
+       v128.store_lane<u64>(rY_ptr, rY2, 0, 32)
+
+       // store r.Z
+       v128.store(rZ_ptr, rZ0)
+       v128.store(rZ_ptr, rZ1, 16)
+       v128.store_lane<u64>(rZ_ptr, rZ2, 0, 32)
+
+       // store r.T
+       v128.store(rT_ptr, rT0)
+       v128.store(rT_ptr, rT1, 16)
+       v128.store_lane<u64>(rT_ptr, rT2, 0, 32)
 }
 
 const ge_sub_p3_q_cached = new ge_cached()
index acb4b1add33b511d6d634b8b94d215ed78ddc0c1..fc46ae7a2e5b484b15c67e9bb2f4498b390e3a99 100644 (file)
@@ -79,6 +79,7 @@ export function fe_add (h: FieldElement, f: FieldElement, g: FieldElement): void
        let g1 = v128.load(g_ptr, 16)
        let g2 = v128.load(g_ptr, 32)
 
+       // h = f + g
        h0 = v128.add<i32>(f0, g0)
        h1 = v128.add<i32>(f1, g1)
        h2 = v128.add<i32>(f2, g2)
@@ -1408,12 +1409,29 @@ export function fe_sq2 (h: FieldElement, f: FieldElement): void {
 //@ts-expect-error
 @inline
 export function fe_sub (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)
-       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)
+
+       let h0: v128, h1: v128, h2: v128
+
+       let f0 = v128.load(f_ptr, 0)
+       let f1 = v128.load(f_ptr, 16)
+       let f2 = v128.load(f_ptr, 32)
+
+       let g0 = v128.load(g_ptr, 0)
+       let g1 = v128.load(g_ptr, 16)
+       let g2 = v128.load(g_ptr, 32)
+
+       // h = f - g
+       h0 = v128.sub<i32>(f0, g0)
+       h1 = v128.sub<i32>(f1, g1)
+       h2 = v128.sub<i32>(f2, g2)
+
+       // store h
+       v128.store(h_ptr, h0, 0)
+       v128.store(h_ptr, h1, 16)
+       v128.store_lane<u64>(h_ptr, h2, 0, 32)
 }
 
 const fe_tobytes_t: FieldElement = fe()
index f56027510effbfc478309d17c08a8c22d20e396e..d859aa6a97d3c9aaa01aaf9861056de3c37d486b 100644 (file)
@@ -83,23 +83,26 @@ export function ge_p2_dbl (r: ge_p1p1, p: ge_p2): void {
 
        // t0 = r.Y²
        fe_sq(rYsq, r.Y)
+       const rYsq0 = v128.load(rYsq_ptr, 0)
+       const rYsq1 = v128.load(rYsq_ptr, 16)
+       const rYsq2 = v128.load(rYsq_ptr, 32)
 
-       // Y = Z + X
+       // rY = rZ + rX
        rY0 = v128.add<i32>(rZ0, rX0)
        rY1 = v128.add<i32>(rZ1, rX1)
        rY2 = v128.add<i32>(rZ2, rX2)
 
-       // Z = Z - X
+       // rZ = rZ - rX
        rZ0 = v128.sub<i32>(rZ0, rX0)
        rZ1 = v128.sub<i32>(rZ1, rX1)
        rZ2 = v128.sub<i32>(rZ2, rX2)
 
-       // X = Y^2 - Y
-       rX0 = v128.sub<i32>(v128.load(rYsq_ptr), rY0)
-       rX1 = v128.sub<i32>(v128.load(rYsq_ptr, 16), rY1)
-       rX2 = v128.sub<i32>(v128.load(rYsq_ptr, 32), rY2)
+       // rX = rYsq - rY
+       rX0 = v128.sub<i32>(rYsq0, rY0)
+       rX1 = v128.sub<i32>(rYsq1, rY1)
+       rX2 = v128.sub<i32>(rYsq2, rY2)
 
-       // T = T - Z
+       // rT = rT - rZ
        rT0 = v128.sub<i32>(rT0, rZ0)
        rT1 = v128.sub<i32>(rT1, rZ1)
        rT2 = v128.sub<i32>(rT2, rZ2)