]> git.codecow.com Git - nano25519.git/commitdiff
Refactor from tweetnacl 16x16 radix limbs to libsodium 10x25.5-bit radix limb impleme...
authorChris Duncan <chris@zoso.dev>
Sat, 7 Mar 2026 07:37:59 +0000 (23:37 -0800)
committerChris Duncan <chris@zoso.dev>
Sat, 7 Mar 2026 07:37:59 +0000 (23:37 -0800)
assembly/base.ts
assembly/fe.ts
assembly/ge.ts
assembly/nano-nacl.ts
assembly/sc.ts
assembly/utils.ts
index.html
index.ts

index 74079af25876fbceb17bf4cbf17d714dea2ff856..c9a72936712a1d75e4ed8411b4cee5d64fd2b520 100644 (file)
@@ -1350,3 +1350,46 @@ export const base = StaticArray.fromArray<StaticArray<ge_precomp>>([
                }
        ])
 ])
+
+export const base2 = StaticArray.fromArray<ge_precomp>([
+       {
+               yplusx: fe([25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605]),
+               yminusx: fe([-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378]),
+               xy2d: fe([-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546])
+       },
+       {
+               yplusx: fe([15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024]),
+               yminusx: fe([16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574]),
+               xy2d: fe([30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357])
+       },
+       {
+               yplusx: fe([10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380]),
+               yminusx: fe([4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306]),
+               xy2d: fe([19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942])
+       },
+       {
+               yplusx: fe([5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766]),
+               yminusx: fe([-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701]),
+               xy2d: fe([28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300])
+       },
+       {
+               yplusx: fe([-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877]),
+               yminusx: fe([-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951]),
+               xy2d: fe([4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784])
+       },
+       {
+               yplusx: fe([-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436]),
+               yminusx: fe([25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918]),
+               xy2d: fe([23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877])
+       },
+       {
+               yplusx: fe([-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800]),
+               yminusx: fe([-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305]),
+               xy2d: fe([-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300])
+       },
+       {
+               yplusx: fe([-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876]),
+               yminusx: fe([-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619]),
+               xy2d: fe([-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683])
+       }
+])
index dad60b76b5bb1657b804fe3641ce0dc0242c83be..f2170b0410dfe066e8f826d3695d774936b1f3de 100644 (file)
  *
  * `p = 2²⁵⁵-19`
  *
- * They are represented as 16 limbs of 16-bit values so that
+ * They are represented as 10 limbs of alternating 25- and 26-bit values so that
  *
- * `n = 256⁰a[0] + 256¹a[1] + ... + 256³¹a[31]`
+ * `n = 2⁰a[0] + 2²⁶a[1] + 2⁷⁷a[2] ... + 2²³⁰a[9]`
  */
-
-import { load_3, load_4 } from "./utils"
+import { load_3, load_4, sodium_is_zero } from "./utils"
 
 /**
- * 16-wide x 16-bit array of values representing some large integer `n mod p`.
+ * 10x 25- and 26-bit array of values representing a large integer `n mod p`.
  */
 export type FieldElement = StaticArray<i32>
-export function fe (source: Array<i32> = new Array<i32>(16)): FieldElement {
+export function fe (source: Array<i32> = new Array<i32>(10)): FieldElement {
        return StaticArray.fromArray<i32>(source)
 }
 
@@ -33,7 +32,7 @@ export function fe (source: Array<i32> = new Array<i32>(16)): FieldElement {
 //@ts-expect-error
 @inline
 export function fe_0 (h: FieldElement): void {
-       memory.fill(changetype<usize>(h), 0, 64)
+       memory.fill(changetype<usize>(h), 0, 40)
 }
 
 /**
@@ -70,13 +69,9 @@ export function fe_add (h: FieldElement, f: FieldElement, g: FieldElement): void
        bi = v128.load(b, 16)
        v128.store(o, v128.add<i32>(ai, bi), 16)
 
-       ai = v128.load(a, 32)
-       bi = v128.load(b, 32)
-       v128.store(o, v128.add<i32>(ai, bi), 32)
-
-       ai = v128.load(a, 48)
-       bi = v128.load(b, 48)
-       v128.store(o, v128.add<i32>(ai, bi), 48)
+       ai = i64x2(load<i64>(a + 32), 0)
+       bi = i64x2(load<i64>(b + 32), 0)
+       v128.store_lane<i64>(o, v128.add<i32>(ai, bi), 0, 32)
 }
 
 /**
@@ -102,13 +97,9 @@ export function fe_cmov (f: FieldElement, g: FieldElement, b: u64): void {
        qi = v128.load(q, 16)
        v128.store(p, v128.bitselect(qi, pi, c), 16)
 
-       pi = v128.load(p, 32)
-       qi = v128.load(q, 32)
-       v128.store(p, v128.bitselect(qi, pi, c), 32)
-
-       pi = v128.load(p, 48)
-       qi = v128.load(q, 48)
-       v128.store(p, v128.bitselect(qi, pi, c), 48)
+       pi = i64x2(load<i64>(p + 32), 0)
+       qi = i64x2(load<i64>(q + 32), 0)
+       v128.store_lane<u64>(p, v128.bitselect(qi, pi, c), 0, 32)
 }
 
 /**
@@ -120,7 +111,7 @@ export function fe_cmov (f: FieldElement, g: FieldElement, b: u64): void {
 //@ts-expect-error
 @inline
 export function fe_copy (h: FieldElement, f: FieldElement): void {
-       memory.copy(changetype<usize>(h), changetype<usize>(f), 64)
+       memory.copy(changetype<usize>(h), changetype<usize>(f), 40)
 }
 
 /**
@@ -148,15 +139,32 @@ export function fe_cswap (f: FieldElement, g: FieldElement, b: u64): void {
        v128.store(p, v128.bitselect(qi, pi, c), 16)
        v128.store(q, v128.bitselect(pi, qi, c), 16)
 
-       pi = v128.load(p, 32)
-       qi = v128.load(q, 32)
-       v128.store(p, v128.bitselect(qi, pi, c), 32)
-       v128.store(q, v128.bitselect(pi, qi, c), 32)
+       pi = i64x2(load<i64>(p + 32), 0)
+       qi = i64x2(load<i64>(q + 32), 0)
+       v128.store_lane<i64>(p, v128.bitselect(qi, pi, c), 0, 32)
+       v128.store_lane<i64>(q, v128.bitselect(pi, qi, c), 0, 32)
+}
+
+/**
+ * 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 o = changetype<usize>(h)
+       const a = changetype<usize>(f)
+
+       let ai = v128.load(a)
+       v128.store(o, v128.add<i32>(ai, ai))
+
+       ai = v128.load(a, 16)
+       v128.store(o, v128.add<i32>(ai, ai), 16)
 
-       pi = v128.load(p, 48)
-       qi = v128.load(q, 48)
-       v128.store(p, v128.bitselect(qi, pi, c), 48)
-       v128.store(q, v128.bitselect(pi, qi, c), 48)
+       ai = i64x2(load<i64>(a + 32), 0)
+       v128.store_lane<i64>(o, v128.add<i32>(ai, ai), 0, 32)
 }
 
 /**
@@ -187,35 +195,35 @@ export function fe_frombytes (h: FieldElement, s: StaticArray<u8>): void {
 
        carry9 = (h9 + i64(1 << 24)) >> 25
        h0 += carry9 * 19
-       h9 -= carry9 << 25
+       h9 -= carry9 * u64(1 << 25)
        carry1 = (h1 + i64(1 << 24)) >> 25
        h2 += carry1
-       h1 -= carry1 << 25
+       h1 -= carry1 * u64(1 << 25)
        carry3 = (h3 + i64(1 << 24)) >> 25
        h4 += carry3
-       h3 -= carry3 << 25
+       h3 -= carry3 * u64(1 << 25)
        carry5 = (h5 + i64(1 << 24)) >> 25
        h6 += carry5
-       h5 -= carry5 << 25
+       h5 -= carry5 * u64(1 << 25)
        carry7 = (h7 + i64(1 << 24)) >> 25
        h8 += carry7
-       h7 -= carry7 << 25
+       h7 -= carry7 * u64(1 << 25)
 
        carry0 = (h0 + i64(1 << 25)) >> 26
        h1 += carry0
-       h0 -= carry0 << 26
+       h0 -= carry0 * u64(1 << 26)
        carry2 = (h2 + i64(1 << 25)) >> 26
        h3 += carry2
-       h2 -= carry2 << 26
+       h2 -= carry2 * u64(1 << 26)
        carry4 = (h4 + i64(1 << 25)) >> 26
        h5 += carry4
-       h4 -= carry4 << 26
+       h4 -= carry4 * u64(1 << 26)
        carry6 = (h6 + i64(1 << 25)) >> 26
        h7 += carry6
-       h6 -= carry6 << 26
+       h6 -= carry6 * u64(1 << 26)
        carry8 = (h8 + i64(1 << 25)) >> 26
        h9 += carry8
-       h8 -= carry8 << 26
+       h8 -= carry8 * u64(1 << 26)
 
        h[0] = i32(h0)
        h[1] = i32(h1)
@@ -229,13 +237,13 @@ export function fe_frombytes (h: FieldElement, s: StaticArray<u8>): void {
        h[9] = i32(h9)
 }
 
-/*
- * Inversion - sets out to 0 if z=0
- */
 const t0: FieldElement = fe()
 const t1: FieldElement = fe()
 const t2: FieldElement = fe()
 const t3: FieldElement = fe()
+/**
+ * Inversion - sets out to 0 if z=0
+ */
 export function fe_invert (out: FieldElement, z: FieldElement): void {
        fe_sq(t0, z)
        fe_sq(t1, t0)
@@ -283,14 +291,14 @@ export function fe_invert (out: FieldElement, z: FieldElement): void {
        fe_mul(out, t1, t0)
 }
 
-/*
- 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.1*2²⁶,1.1*2²⁵,1.1*2²⁶,1.1*2²⁵,etc.
- */
 const fe_isnegative_s = new StaticArray<u8>(32)
+/**
+ * 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.1*2²⁶,1.1*2²⁵,1.1*2²⁶,1.1*2²⁵,etc.
+ */
 //@ts-expect-error
 @inline
 export function fe_isnegative (f: FieldElement): u8 {
@@ -299,7 +307,23 @@ export function fe_isnegative (f: FieldElement): u8 {
        return s[0] & 1
 }
 
-const multiply_t = new StaticArray<i64>(32)
+const fe_iszero_s = new StaticArray<u8>(32)
+/**
+ * return 1 if f == 0
+ * return 0 if f != 0
+ *
+ * Preconditions:
+ * |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,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)
+}
+
+const multiply_t = new StaticArray<i64>(24)
 /**
  * Multiply two FieldElements and store the product.
  *
@@ -307,27 +331,27 @@ const multiply_t = new StaticArray<i64>(32)
  * @param f FieldElement multiplicand source
  * @param g FieldElement multiplicand source
  */
-//@ts-expect-error
-@inline
 export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void {
        const o = changetype<usize>(h)
        const a = changetype<usize>(f)
        const b = changetype<usize>(g)
 
        const t = changetype<usize>(multiply_t)
-       memory.fill(t, 0, 256)
+       memory.fill(t, 0, 160)
 
-       const b0 = v128.load(b)
-       const b4 = v128.load(b + 16)
-       const b8 = v128.load(b + 32)
-       const b12 = v128.load(b + 48)
+       const b0: v128 = v128.load(b)
+       const b4: v128 = v128.load(b + 16)
+       const b8: v128 = i32x4(load<i32>(b + 32), load<i32>(b + 36), 0, 0)
 
-       for (let i = 0; i < 16; i++) {
+       for (let i = 0; i < 10; i++) {
+               const odd = (i & 1) + 1
                const ai = v128.splat<i32>(load<i32>(a + (i << 2)))
 
                let ptr = t + (usize(i) << 3)
                let pLo = i64x2.extmul_low_i32x4_s(ai, b0)
                let pHi = i64x2.extmul_high_i32x4_s(ai, b0)
+               pLo = v128.mul<i64>(pLo, i64x2(1, odd))
+               pHi = v128.mul<i64>(pHi, i64x2(1, odd))
                let tLo = v128.load(ptr)
                let tHi = v128.load(ptr + 16)
                tLo = i64x2.add(tLo, pLo)
@@ -338,6 +362,8 @@ export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void
                ptr += 32
                pLo = i64x2.extmul_low_i32x4_s(ai, b4)
                pHi = i64x2.extmul_high_i32x4_s(ai, b4)
+               pLo = v128.mul<i64>(pLo, i64x2(1, odd))
+               pHi = v128.mul<i64>(pHi, i64x2(1, odd))
                tLo = v128.load(ptr)
                tHi = v128.load(ptr + 16)
                tLo = i64x2.add(tLo, pLo)
@@ -348,16 +374,8 @@ export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void
                ptr += 32
                pLo = i64x2.extmul_low_i32x4_s(ai, b8)
                pHi = i64x2.extmul_high_i32x4_s(ai, b8)
-               tLo = v128.load(ptr)
-               tHi = v128.load(ptr + 16)
-               tLo = i64x2.add(tLo, pLo)
-               tHi = i64x2.add(tHi, pHi)
-               v128.store(ptr, tLo)
-               v128.store(ptr + 16, tHi)
-
-               ptr += 32
-               pLo = i64x2.extmul_low_i32x4_s(ai, b12)
-               pHi = i64x2.extmul_high_i32x4_s(ai, b12)
+               pLo = v128.mul<i64>(pLo, i64x2(1, odd))
+               pHi = v128.mul<i64>(pHi, i64x2(1, odd))
                tLo = v128.load(ptr)
                tHi = v128.load(ptr + 16)
                tLo = i64x2.add(tLo, pLo)
@@ -365,6 +383,8 @@ export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void
                v128.store(ptr, tLo)
                v128.store(ptr + 16, tHi)
        }
+       // discard last 4 elements since we do not actually need them
+       memory.fill(t + 160, 0, 32)
        normalize(o, t)
 }
 
@@ -386,37 +406,91 @@ export function fe_neg (h: FieldElement, f: FieldElement): void {
        ai = v128.load(a, 16)
        v128.store(o, v128.neg<i32>(ai), 16)
 
-       ai = v128.load(a, 32)
-       v128.store(o, v128.neg<i32>(ai), 32)
-
-       ai = v128.load(a, 48)
-       v128.store(o, v128.neg<i32>(ai), 48)
+       ai = i64x2(load<i64>(a + 32), 0)
+       v128.store_lane<i64>(o, v128.neg<i32>(ai), 0, 32)
 }
 
-/*
- Preconditions:
- |h| bounded by 1.1*2²⁶,1.1*2²⁵,1.1*2²⁶,1.1*2²⁵,etc.
-
- Write p=2²⁵⁵-19; q=floor(h/p).
- Basic claim: q = floor(2⁻²⁵⁵(h + 19 2^(-25)h9 + 2^(-1))).
-
- 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^(-1)-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.
+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^252-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
 
- Write x=r+19(2⁻²⁵⁵)r+y.
- Then 0 < x < 2²⁵⁵ so floor(2⁻²⁵⁵x) = 0 so floor(q+2⁻²⁵⁵x) = q.
+       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)
+}
 
- Have q+2⁻²⁵⁵x = 2⁻²⁵⁵(h + 19 2^(-25) h9 + 2^(-1))
- so floor(2⁻²⁵⁵(h + 19 2^(-25) h9 + 2^(-1))) = q.
-*/
+/**
+ * Preconditions:
+ * |h| bounded by 1.1*2²⁶,1.1*2²⁵,1.1*2²⁶,1.1*2²⁵,etc.
+ *
+ * Write p=2²⁵⁵-19; q=floor(h/p).
+ * Basic claim: q = floor(2⁻²⁵⁵(h + 19 2^(-25)h9 + 2^(-1))).
+ *
+ * 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^(-1)-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^(-25) h9 + 2^(-1))
+ * so floor(2⁻²⁵⁵(h + 19 2^(-25) h9 + 2^(-1))) = q.
+ */
 export function fe_reduce (h: FieldElement, f: FieldElement): void {
        let h0 = f[0]
        let h1 = f[1]
@@ -431,7 +505,7 @@ export function fe_reduce (h: FieldElement, f: FieldElement): void {
        let q: i32
        let carry0: i32, carry1: i32, carry2: i32, carry3: i32, carry4: i32, carry5: i32, carry6: i32, carry7: i32, carry8: i32, carry9: i32
 
-       q = (19 * h9 + (i32(1) << 24)) >> 25
+       q = (19 * h9 + (u32(1) << 24)) >> 25
        q = (h0 + q) >> 26
        q = (h1 + q) >> 25
        q = (h2 + q) >> 26
@@ -449,33 +523,33 @@ export function fe_reduce (h: FieldElement, f: FieldElement): void {
 
        carry0 = h0 >> 26
        h1 += carry0
-       h0 -= carry0 << 26
+       h0 -= carry0 * u32(1 << 26)
        carry1 = h1 >> 25
        h2 += carry1
-       h1 -= carry1 << 25
+       h1 -= carry1 * u32(1 << 25)
        carry2 = h2 >> 26
        h3 += carry2
-       h2 -= carry2 << 26
+       h2 -= carry2 * u32(1 << 26)
        carry3 = h3 >> 25
        h4 += carry3
-       h3 -= carry3 << 25
+       h3 -= carry3 * u32(1 << 25)
        carry4 = h4 >> 26
        h5 += carry4
-       h4 -= carry4 << 26
+       h4 -= carry4 * u32(1 << 26)
        carry5 = h5 >> 25
        h6 += carry5
-       h5 -= carry5 << 25
+       h5 -= carry5 * u32(1 << 25)
        carry6 = h6 >> 26
        h7 += carry6
-       h6 -= carry6 << 26
+       h6 -= carry6 * u32(1 << 26)
        carry7 = h7 >> 25
        h8 += carry7
-       h7 -= carry7 << 25
+       h7 -= carry7 * u32(1 << 25)
        carry8 = h8 >> 26
        h9 += carry8
-       h8 -= carry8 << 26
+       h8 -= carry8 * u32(1 << 26)
        carry9 = h9 >> 25
-       h9 -= carry9 << 25
+       h9 -= carry9 * u32(1 << 25)
 
        h[0] = h0
        h[1] = h1
@@ -536,15 +610,12 @@ export function fe_sub (h: FieldElement, f: FieldElement, g: FieldElement): void
        bi = v128.load(b, 16)
        v128.store(o, v128.sub<i32>(ai, bi), 16)
 
-       ai = v128.load(a, 32)
-       bi = v128.load(b, 32)
-       v128.store(o, v128.sub<i32>(ai, bi), 32)
-
-       ai = v128.load(a, 48)
-       bi = v128.load(b, 48)
-       v128.store(o, v128.sub<i32>(ai, bi), 48)
+       ai = i64x2(load<i64>(a + 32), 0)
+       bi = i64x2(load<i64>(b + 32), 0)
+       v128.store_lane<i64>(o, v128.sub<i32>(ai, bi), 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;
@@ -552,38 +623,38 @@ export function fe_sub (h: FieldElement, f: FieldElement, g: FieldElement): void
  *
  * Goal: Output h0+...+2²³⁰ h9.
  */
-const t: FieldElement = fe()
 export function fe_tobytes (s: StaticArray<u8>, h: FieldElement): void {
+       const t = fe_tobytes_t
        fe_reduce(t, h)
        s[0] = u8(t[0] >> 0)
        s[1] = u8(t[0] >> 8)
        s[2] = u8(t[0] >> 16)
-       s[3] = u8((t[0] >> 24) | (t[1] * (i32(1) << 2)))
+       s[3] = u8((t[0] >> 24) | (t[1] * (u32(1) << 2)))
        s[4] = u8(t[1] >> 6)
        s[5] = u8(t[1] >> 14)
-       s[6] = u8((t[1] >> 22) | (t[2] * (i32(1) << 3)))
+       s[6] = u8((t[1] >> 22) | (t[2] * (u32(1) << 3)))
        s[7] = u8(t[2] >> 5)
        s[8] = u8(t[2] >> 13)
-       s[9] = u8((t[2] >> 21) | (t[3] * (i32(1) << 5)))
+       s[9] = u8((t[2] >> 21) | (t[3] * (u32(1) << 5)))
        s[10] = u8(t[3] >> 3)
        s[11] = u8(t[3] >> 11)
-       s[12] = u8((t[3] >> 19) | (t[4] * (i32(1) << 6)))
+       s[12] = u8((t[3] >> 19) | (t[4] * (u32(1) << 6)))
        s[13] = u8(t[4] >> 2)
        s[14] = u8(t[4] >> 10)
        s[15] = u8(t[4] >> 18)
        s[16] = u8(t[5] >> 0)
        s[17] = u8(t[5] >> 8)
        s[18] = u8(t[5] >> 16)
-       s[19] = u8((t[5] >> 24) | (t[6] * (i32(1) << 1)))
+       s[19] = u8((t[5] >> 24) | (t[6] * (u32(1) << 1)))
        s[20] = u8(t[6] >> 7)
        s[21] = u8(t[6] >> 15)
-       s[22] = u8((t[6] >> 23) | (t[7] * (i32(1) << 3)))
+       s[22] = u8((t[6] >> 23) | (t[7] * (u32(1) << 3)))
        s[23] = u8(t[7] >> 5)
        s[24] = u8(t[7] >> 13)
-       s[25] = u8((t[7] >> 21) | (t[8] * (i32(1) << 4)))
+       s[25] = u8((t[7] >> 21) | (t[8] * (u32(1) << 4)))
        s[26] = u8(t[8] >> 4)
        s[27] = u8(t[8] >> 12)
-       s[28] = u8((t[8] >> 20) | (t[9] * (i32(1) << 6)))
+       s[28] = u8((t[8] >> 20) | (t[9] * (u32(1) << 6)))
        s[29] = u8(t[9] >> 2)
        s[30] = u8(t[9] >> 10)
        s[31] = u8(t[9] >> 18)
@@ -612,198 +683,126 @@ export function fe_tobytes (s: StaticArray<u8>, h: FieldElement): void {
 //@ts-expect-error
 @inline
 function normalize (o: usize, t: usize): void {
-       // reduce
+       // reduce from 20 limbs to 10
        let x = load<i64>(t)
-       let y = load<i64>(t, 128)
-       store<i64>(t, x + (38 * y))
+       let y = load<i64>(t, 80)
+       store<i64>(t, x + (19 * y))
 
        x = load<i64>(t, 8)
-       y = load<i64>(t, 136)
-       store<i64>(t, x + (38 * y), 8)
+       y = load<i64>(t, 88)
+       store<i64>(t, x + (19 * y), 8)
 
        x = load<i64>(t, 16)
-       y = load<i64>(t, 144)
-       store<i64>(t, x + (38 * y), 16)
+       y = load<i64>(t, 96)
+       store<i64>(t, x + (19 * y), 16)
 
        x = load<i64>(t, 24)
-       y = load<i64>(t, 152)
-       store<i64>(t, x + (38 * y), 24)
+       y = load<i64>(t, 104)
+       store<i64>(t, x + (19 * y), 24)
 
        x = load<i64>(t, 32)
-       y = load<i64>(t, 160)
-       store<i64>(t, x + (38 * y), 32)
+       y = load<i64>(t, 112)
+       store<i64>(t, x + (19 * y), 32)
 
        x = load<i64>(t, 40)
-       y = load<i64>(t, 168)
-       store<i64>(t, x + (38 * y), 40)
+       y = load<i64>(t, 120)
+       store<i64>(t, x + (19 * y), 40)
 
        x = load<i64>(t, 48)
-       y = load<i64>(t, 176)
-       store<i64>(t, x + (38 * y), 48)
+       y = load<i64>(t, 128)
+       store<i64>(t, x + (19 * y), 48)
 
        x = load<i64>(t, 56)
-       y = load<i64>(t, 184)
-       store<i64>(t, x + (38 * y), 56)
+       y = load<i64>(t, 136)
+       store<i64>(t, x + (19 * y), 56)
 
        x = load<i64>(t, 64)
-       y = load<i64>(t, 192)
-       store<i64>(t, x + (38 * y), 64)
+       y = load<i64>(t, 144)
+       store<i64>(t, x + (19 * y), 64)
 
        x = load<i64>(t, 72)
-       y = load<i64>(t, 200)
-       store<i64>(t, x + (38 * y), 72)
-
-       x = load<i64>(t, 80)
-       y = load<i64>(t, 208)
-       store<i64>(t, x + (38 * y), 80)
-
-       x = load<i64>(t, 88)
-       y = load<i64>(t, 216)
-       store<i64>(t, x + (38 * y), 88)
-
-       x = load<i64>(t, 96)
-       y = load<i64>(t, 224)
-       store<i64>(t, x + (38 * y), 96)
-
-       x = load<i64>(t, 104)
-       y = load<i64>(t, 232)
-       store<i64>(t, x + (38 * y), 104)
-
-       x = load<i64>(t, 112)
-       y = load<i64>(t, 240)
-       store<i64>(t, x + (38 * y), 112)
+       y = load<i64>(t, 152)
+       store<i64>(t, x + (19 * y), 72)
 
        // first carry
-       let c: i64 = load<i64>(t)
-       store<i64>(t, c & 0xFFFF)
-       c >>= 16
-
-       c += load<i64>(t, 8)
-       store<i64>(t, c & 0xFFFF, 8)
-       c >>= 16
-
-       c += load<i64>(t, 16)
-       store<i64>(t, c & 0xFFFF, 16)
-       c >>= 16
-
-       c += load<i64>(t, 24)
-       store<i64>(t, c & 0xFFFF, 24)
-       c >>= 16
-
-       c += load<i64>(t, 32)
-       store<i64>(t, c & 0xFFFF, 32)
-       c >>= 16
-
-       c += load<i64>(t, 40)
-       store<i64>(t, c & 0xFFFF, 40)
-       c >>= 16
-
-       c += load<i64>(t, 48)
-       store<i64>(t, c & 0xFFFF, 48)
-       c >>= 16
-
-       c += load<i64>(t, 56)
-       store<i64>(t, c & 0xFFFF, 56)
-       c >>= 16
-
-       c += load<i64>(t, 64)
-       store<i64>(t, c & 0xFFFF, 64)
-       c >>= 16
-
-       c += load<i64>(t, 72)
-       store<i64>(t, c & 0xFFFF, 72)
-       c >>= 16
-
-       c += load<i64>(t, 80)
-       store<i64>(t, c & 0xFFFF, 80)
-       c >>= 16
-
-       c += load<i64>(t, 88)
-       store<i64>(t, c & 0xFFFF, 88)
-       c >>= 16
-
-       c += load<i64>(t, 96)
-       store<i64>(t, c & 0xFFFF, 96)
-       c >>= 16
-
-       c += load<i64>(t, 104)
-       store<i64>(t, c & 0xFFFF, 104)
-       c >>= 16
-
-       c += load<i64>(t, 112)
-       store<i64>(t, c & 0xFFFF, 112)
-       c >>= 16
-
-       c += load<i64>(t, 120)
-       store<i64>(t, c & 0xFFFF, 120)
-       c >>= 16
-
-       store<i64>(t, load<i64>(t) + (38 * c))
-
-       // second carry and assign result to output
-       c = load<i64>(t)
-       store<i32>(o, c & 0xFFFF)
-       c >>= 16
-
-       c += load<i64>(t, 8)
-       store<i32>(o, c & 0xFFFF, 4)
-       c >>= 16
-
-       c += load<i64>(t, 16)
-       store<i32>(o, c & 0xFFFF, 8)
-       c >>= 16
-
-       c += load<i64>(t, 24)
-       store<i32>(o, c & 0xFFFF, 12)
-       c >>= 16
-
-       c += load<i64>(t, 32)
-       store<i32>(o, c & 0xFFFF, 16)
-       c >>= 16
-
-       c += load<i64>(t, 40)
-       store<i32>(o, c & 0xFFFF, 20)
-       c >>= 16
-
-       c += load<i64>(t, 48)
-       store<i32>(o, c & 0xFFFF, 24)
-       c >>= 16
-
-       c += load<i64>(t, 56)
-       store<i32>(o, c & 0xFFFF, 28)
-       c >>= 16
-
-       c += load<i64>(t, 64)
-       store<i32>(o, c & 0xFFFF, 32)
-       c >>= 16
-
-       c += load<i64>(t, 72)
-       store<i32>(o, c & 0xFFFF, 36)
-       c >>= 16
-
-       c += load<i64>(t, 80)
-       store<i32>(o, c & 0xFFFF, 40)
-       c >>= 16
-
-       c += load<i64>(t, 88)
-       store<i32>(o, c & 0xFFFF, 44)
-       c >>= 16
-
-       c += load<i64>(t, 96)
-       store<i32>(o, c & 0xFFFF, 48)
-       c >>= 16
-
-       c += load<i64>(t, 104)
-       store<i32>(o, c & 0xFFFF, 52)
-       c >>= 16
-
-       c += load<i64>(t, 112)
-       store<i32>(o, c & 0xFFFF, 56)
-       c >>= 16
-
-       c += load<i64>(t, 120)
-       store<i32>(o, c & 0xFFFF, 60)
-       c >>= 16
-
-       store<i64>(o, load<i64>(o) + (38 * c))
+       let v: i64 = load<i64>(t, 0)
+       let c: i64 = (v + (1 << 25)) >> 26
+       store<i64>(t, v - (c << 26), 0)
+
+       v = load<i64>(t, 8) + c
+       c = (v + (1 << 24)) >> 25
+       store<i64>(t, v - (c << 25), 8)
+
+       v = load<i64>(t, 16) + c
+       c = (v + (1 << 25)) >> 26
+       store<i64>(t, v - (c << 26), 16)
+
+       v = load<i64>(t, 24) + c
+       c = (v + (1 << 24)) >> 25
+       store<i64>(t, v - (c << 25), 24)
+
+       v = load<i64>(t, 32) + c
+       c = (v + (1 << 25)) >> 26
+       store<i64>(t, v - (c << 26), 32)
+
+       v = load<i64>(t, 40) + c
+       c = (v + (1 << 24)) >> 25
+       store<i64>(t, v - (c << 25), 40)
+
+       v = load<i64>(t, 48) + c
+       c = (v + (1 << 25)) >> 26
+       store<i64>(t, v - (c << 26), 48)
+
+       v = load<i64>(t, 56) + c
+       c = (v + (1 << 24)) >> 25
+       store<i64>(t, v - (c << 25), 56)
+
+       v = load<i64>(t, 64) + c
+       c = (v + (1 << 25)) >> 26
+       store<i64>(t, v - (c << 26), 64)
+
+       v = load<i64>(t, 72) + c
+       c = (v + (1 << 24)) >> 25
+       store<i64>(t, v - (c << 25), 72)
+
+       // fold final carry back to start then do second carry and assign to output
+       v = load<i64>(t, 0) + (19 * c)
+       c = (v + (1 << 25)) >> 26
+       store<i32>(o, v - (c << 26), 0)
+
+       v = load<i64>(t, 8) + c
+       c = (v + (1 << 24)) >> 25
+       store<i32>(o, v - (c << 25), 4)
+
+       v = load<i64>(t, 16) + c
+       c = (v + (1 << 25)) >> 26
+       store<i32>(o, v - (c << 26), 8)
+
+       v = load<i64>(t, 24) + c
+       c = (v + (1 << 24)) >> 25
+       store<i32>(o, v - (c << 25), 12)
+
+       v = load<i64>(t, 32) + c
+       c = (v + (1 << 25)) >> 26
+       store<i32>(o, v - (c << 26), 16)
+
+       v = load<i64>(t, 40) + c
+       c = (v + (1 << 24)) >> 25
+       store<i32>(o, v - (c << 25), 20)
+
+       v = load<i64>(t, 48) + c
+       c = (v + (1 << 25)) >> 26
+       store<i32>(o, v - (c << 26), 24)
+
+       v = load<i64>(t, 56) + c
+       c = (v + (1 << 24)) >> 25
+       store<i32>(o, v - (c << 25), 28)
+
+       v = load<i64>(t, 64) + c
+       c = (v + (1 << 25)) >> 26
+       store<i32>(o, v - (c << 26), 32)
+
+       v = load<i64>(t, 72) + c
+       c = (v + (1 << 24)) >> 25
+       store<i32>(o, v - (c << 25), 36)
 }
index a3dfefa0d5da059b485bad26a8f5d1049d867e5a..386a7c93ef02611bbc2839f62f00e30978acfb04 100644 (file)
@@ -1,11 +1,6 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { base } from './base'
-import { ed25519_d2 } from './constants'
-import { fe, fe_0, fe_1, fe_add, fe_cmov, fe_copy, fe_invert, fe_isnegative, fe_mul, fe_neg, fe_sq, fe_sq2, fe_sub, fe_tobytes, FieldElement } from './fe'
-import { equal, negative } from './utils'
-
 /**
  * ge means group element.
  *
@@ -21,6 +16,10 @@ import { equal, negative } from './utils'
  * - ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
  * - ge_precomp (Duif): (y+x,y-x,2dxy)
 */
+import { base, base2 } from './base'
+import { ed25519_d, ed25519_d2, fe25519_sqrtm1 } from './constants'
+import { fe, fe_0, fe_1, fe_add, fe_cmov, fe_copy, fe_frombytes, fe_invert, fe_isnegative, fe_iszero, fe_mul, fe_neg, fe_pow22523, fe_sq, fe_sq2, fe_sub, fe_tobytes, FieldElement } from './fe'
+import { equal, negative, optblocker_u8 } from './utils'
 
 export class ge_p2 {
        X: FieldElement = fe()
@@ -51,6 +50,21 @@ export class ge_cached {
        T2d: FieldElement = fe()
 }
 
+//@ts-expect-error
+@inline
+function ge_cached_8 (): StaticArray<ge_cached> {
+       return StaticArray.fromArray<ge_cached>([
+               new ge_cached(),
+               new ge_cached(),
+               new ge_cached(),
+               new ge_cached(),
+               new ge_cached(),
+               new ge_cached(),
+               new ge_cached(),
+               new ge_cached()
+       ])
+}
+
 //@ts-expect-error
 @inline
 export function ge_p2_0 (h: ge_p2): void {
@@ -69,19 +83,18 @@ export function ge_p3_0 (h: ge_p3): void {
 //@ts-expect-error
 @inline
 export function ge_precomp_0 (h: ge_precomp): void {
-       fe_0(h.yplusx)
-       fe_0(h.yminusx)
+       fe_1(h.yplusx)
+       fe_1(h.yminusx)
        fe_0(h.xy2d)
 }
 
-
-function ge_cmov (t: ge_precomp, u: ge_precomp, b: i8): void {
+function ge_cmov (t: ge_precomp, u: ge_precomp, b: u8): void {
        fe_cmov(t.yplusx, u.yplusx, b)
        fe_cmov(t.yminusx, u.yminusx, b)
        fe_cmov(t.xy2d, u.xy2d, b)
 }
 
-const ge25519_cmov8_minust = new ge_precomp()
+const ge25519_cmov8_minust: ge_precomp = new ge_precomp()
 function ge_cmov8 (t: ge_precomp, precomp: StaticArray<ge_precomp>, b: i8): void {
        const minust = ge25519_cmov8_minust
        const bnegative: u8 = negative(b)
@@ -107,10 +120,10 @@ function ge_cmov8_base (t: ge_precomp, pos: i32, b: i8): void {
        ge_cmov8(t, base[pos], b)
 }
 
+const ge_add_precomp_t0: FieldElement = fe()
 /**
  * r = p + q
  */
-const ge_add_precomp_t0: FieldElement = fe()
 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)
@@ -152,10 +165,10 @@ function ge_p2_dbl (r: ge_p1p1, p: ge_p2): void {
        fe_sub(r.T, r.T, r.Z)
 }
 
+const ge_p3_dbl_q: ge_p2 = new ge_p2()
 /**
  * r = 2 * p
  */
-const ge_p3_dbl_q = new ge_p2()
 function ge_p3_dbl (r: ge_p1p1, p: ge_p3): void {
        const q = ge_p3_dbl_q
        ge_p3_to_p2(q, p)
@@ -181,10 +194,10 @@ function ge_p3_to_cached (r: ge_cached, p: ge_p3): void {
        fe_mul(r.T2d, p.T, ed25519_d2)
 }
 
+const ge_add_cached_t0: FieldElement = fe()
 /**
  * r = p + q
  */
-const ge_add_cached_t0: FieldElement = fe()
 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)
@@ -207,7 +220,6 @@ function ge_cached_0 (h: ge_cached): void {
        fe_0(h.T2d)
 }
 
-
 function ge_cmov_cached (t: ge_cached, u: ge_cached, b: u8): void {
        fe_cmov(t.YplusX, u.YplusX, b)
        fe_cmov(t.YminusX, u.YminusX, b)
@@ -254,7 +266,7 @@ const ge_scalarmult_p5: ge_p3 = new ge_p3()
 const ge_scalarmult_p6: ge_p3 = new ge_p3()
 const ge_scalarmult_p7: ge_p3 = new ge_p3()
 const ge_scalarmult_p8: ge_p3 = new ge_p3()
-const ge_scalarmult_pi: StaticArray<ge_cached> = new StaticArray<ge_cached>(8)
+const ge_scalarmult_pi: StaticArray<ge_cached> = ge_cached_8()
 const ge_scalarmult_t: ge_cached = new ge_cached()
 /**
  * `h = a * p`
@@ -360,10 +372,10 @@ export function ge_scalarmult (h: ge_p3, a: StaticArray<u8>, p: ge_p3): void {
        ge_p1p1_to_p3(h, r)
 }
 
-const ge_scalarmult_base_e: StaticArray<u8> = new StaticArray<u8>(64)
+const ge_scalarmult_base_e: StaticArray<i8> = new StaticArray<i8>(64)
 const ge_scalarmult_base_r: ge_p1p1 = new ge_p1p1()
-const ge_scalarmult_base_s = new ge_p2()
-const ge_scalarmult_base_t = new ge_precomp()
+const ge_scalarmult_base_s: ge_p2 = new ge_p2()
+const ge_scalarmult_base_t: ge_precomp = new ge_precomp()
 /**
  * `h = a * B` (with precomputation)
  *
@@ -380,7 +392,6 @@ const ge_scalarmult_base_t = new ge_precomp()
  */
 export function ge_scalarmult_base (h: ge_p3, a: StaticArray<u8>): void {
        const e = ge_scalarmult_base_e
-       let carry: i8 = 0
        const r = ge_scalarmult_base_r
        const s = ge_scalarmult_base_s
        const t = ge_scalarmult_base_t
@@ -392,11 +403,12 @@ export function ge_scalarmult_base (h: ge_p3, a: StaticArray<u8>): void {
        /* each e[i] is between 0 and 15 */
        /* e[63] is between 0 and 7 */
 
+       let carry: i8 = 0
        for (let i = 0; i < 63; ++i) {
                e[i] += carry
                carry = e[i] + 8
                carry >>= 4
-               e[i] -= carry << 4
+               e[i] -= carry * (i8(1) << 4)
        }
        e[63] += carry
        /* each e[i] is between -8 and 8 */
@@ -442,12 +454,325 @@ export function ge_p3_tobytes (s: StaticArray<u8>, h: ge_p3): void {
 /**
  * true if `s < p`
  */
-export function ge_is_canonical (s: StaticArray<u8>): boolean {
-       let c: u32 = (s[31] & 0x7f) ^ 0x7f
+export function ge_is_canonical (s: StaticArray<u8>): u8 {
+       let c: u8 = (s[31] & 0x7f) ^ 0x7f
        for (let i = 30; i > 0; i--) {
                c |= s[i] ^ 0xff
        }
        c = (c - 1) >> 8
-       const d: u32 = (0xec - u32(s[0])) >> 8
-       return (c & d & 1) == 0
+       const d: u8 = (0xec - s[0]) >> 8
+       return 1 - (c & d & 1)
+}
+
+const u: FieldElement = fe()
+const v: FieldElement = fe()
+const v3: FieldElement = fe()
+const vxx: FieldElement = fe()
+const m_root_check: FieldElement = fe()
+const p_root_check: FieldElement = fe()
+export function ge_frombytes_negate_vartime (h: ge_p3, s: StaticArray<u8>): i32 {
+       fe_frombytes(h.Y, s)
+       fe_1(h.Z)
+       fe_sq(u, h.Y)
+       fe_mul(v, u, ed25519_d)
+       fe_sub(u, u, h.Z) /* u = y^2-1 */
+       fe_add(v, v, h.Z) /* v = dy^2+1 */
+
+       fe_sq(v3, v)
+       fe_mul(v3, v3, v) /* v3 = v^3 */
+       fe_sq(h.X, v3)
+       fe_mul(h.X, h.X, v)
+       fe_mul(h.X, h.X, u) /* x = uv^7 */
+
+       fe_pow22523(h.X, h.X) /* x = (uv^7)^((q-5)/8) */
+       fe_mul(h.X, h.X, v3)
+       fe_mul(h.X, h.X, u) /* x = uv^3(uv^7)^((q-5)/8) */
+
+       fe_sq(vxx, h.X)
+       fe_mul(vxx, vxx, v)
+       fe_sub(m_root_check, vxx, u) /* vx^2-u */
+       if (fe_iszero(m_root_check) == 0) {
+               fe_add(p_root_check, vxx, u) /* vx^2+u */
+               if (fe_iszero(p_root_check) == 0) {
+                       return -1
+               }
+               fe_mul(h.X, h.X, fe25519_sqrtm1)
+       }
+
+       if (fe_isnegative(h.X) == (s[31] >> 7)) { /* vartime function - compiler optimization is fine */
+               fe_neg(h.X, h.X)
+       }
+       fe_mul(h.T, h.X, h.Y)
+
+       return 0
+}
+
+const ge_frombytes_u: FieldElement = fe()
+const ge_frombytes_v: FieldElement = fe()
+const ge_frombytes_vxx: FieldElement = fe()
+const ge_frombytes_m_root_check: FieldElement = fe()
+const ge_frombytes_p_root_check: FieldElement = fe()
+const ge_frombytes_negx: FieldElement = fe()
+const ge_frombytes_x_sqrtm1: FieldElement = fe()
+export function ge_frombytes (h: ge_p3, s: StaticArray<u8>): i32 {
+       const u = ge_frombytes_u
+       const v = ge_frombytes_v
+       const vxx = ge_frombytes_vxx
+       const m_root_check = ge_frombytes_m_root_check
+       const p_root_check = ge_frombytes_p_root_check
+       const negx = ge_frombytes_negx
+       const x_sqrtm1 = ge_frombytes_x_sqrtm1
+       let has_m_root: u8
+       let has_p_root: u8
+
+       fe_frombytes(h.Y, s)
+       fe_1(h.Z)
+       fe_sq(u, h.Y)
+       fe_mul(v, u, ed25519_d)
+       fe_sub(u, u, h.Z) /* u = y^2-1 */
+       fe_add(v, v, h.Z) /* v = dy^2+1 */
+
+       fe_mul(h.X, u, v)
+       fe_pow22523(h.X, h.X)
+       fe_mul(h.X, u, h.X) /* u((uv)^((q-5)/8)) */
+
+       fe_sq(vxx, h.X)
+       fe_mul(vxx, vxx, v)
+       fe_sub(m_root_check, vxx, u) /* vx^2-u */
+       fe_add(p_root_check, vxx, u) /* vx^2+u */
+       has_m_root = fe_iszero(m_root_check)
+       has_p_root = fe_iszero(p_root_check)
+       fe_mul(x_sqrtm1, h.X, fe25519_sqrtm1) /* x*sqrt(-1) */
+       fe_cmov(h.X, x_sqrtm1, 1 - has_m_root)
+
+       fe_neg(negx, h.X)
+       fe_cmov(h.X, negx, fe_isnegative(h.X) ^ (((s[31] >> 5) ^ optblocker_u8) >> 2))
+       fe_mul(h.T, h.X, h.Y)
+
+       return (has_m_root | has_p_root) - 1
+}
+
+const ge_has_small_order_y_sqrtm1: FieldElement = fe()
+const ge_has_small_order_c: FieldElement = fe()
+export function ge_has_small_order (p: ge_p3): i32 {
+       const y_sqrtm1 = ge_has_small_order_y_sqrtm1
+       const c = ge_has_small_order_c
+       let ret: i32 = 0
+
+       ret |= fe_iszero(p.X)
+       ret |= fe_iszero(p.Y)
+       ret |= fe_iszero(p.Z)
+       fe_mul(y_sqrtm1, p.Y, fe25519_sqrtm1)
+       fe_sub(c, y_sqrtm1, p.X)
+       ret |= fe_iszero(c)
+       fe_add(c, y_sqrtm1, p.X)
+       ret |= fe_iszero(c)
+
+       return ret
+}
+
+/**
+ * r = p
+ */
+export function ge_p2_to_p3 (r: ge_p3, p: ge_p2): void {
+       fe_copy(r.X, p.X)
+       fe_copy(r.Y, p.Y)
+       fe_copy(r.Z, p.Z)
+       fe_mul(r.T, p.X, p.Y)
+}
+
+const ge_p3_sub_q_neg: ge_p3 = new ge_p3()
+/* r = p-q */
+export function ge_p3_sub (r: ge_p3, p: ge_p3, q: ge_p3): void {
+       const q_neg = ge_p3_sub_q_neg
+       ge_p3_neg(q_neg, q)
+       ge_p3_add(r, p, q_neg)
+}
+
+/* r = -p */
+function ge_p3_neg (r: ge_p3, p: ge_p3): void {
+       fe_neg(r.X, p.X)
+       fe_copy(r.Y, p.Y)
+       fe_copy(r.Z, p.Z)
+       fe_neg(r.T, p.T)
+}
+
+const ge_p3_add_q_cached = new ge_cached()
+const ge_p3_add_q_p1p1 = new ge_p1p1()
+/* r = p+q */
+function ge_p3_add (r: ge_p3, p: ge_p3, q: ge_p3): void {
+       const q_cached = ge_p3_add_q_cached
+       const p1p1 = ge_p3_add_q_p1p1
+       ge_p3_to_cached(q_cached, q)
+       ge_add_cached(p1p1, p, q_cached)
+       ge_p1p1_to_p3(r, p1p1)
+}
+
+const ge_double_scalarmult_vartime_aslide: StaticArray<i8> = new StaticArray<i8>(256)
+const ge_double_scalarmult_vartime_bslide: StaticArray<i8> = new StaticArray<i8>(256)
+const ge_double_scalarmult_vartime_Ai: StaticArray<ge_cached> = ge_cached_8() /* A,3A,5A,7A,9A,11A,13A,15A */
+const ge_double_scalarmult_vartime_t: ge_p1p1 = new ge_p1p1()
+const ge_double_scalarmult_vartime_u: ge_p3 = new ge_p3()
+const ge_double_scalarmult_vartime_A2: ge_p3 = new ge_p3()
+/**
+ * r = a * A + b * B
+ * where a = a[0]+256*a[1]+...+256^31 a[31].
+ * and b = b[0]+256*b[1]+...+256^31 b[31].
+ * B is the Ed25519 base point (x,4/5) with x positive.
+ *
+ * Only used for signatures verification.
+ */
+export function ge_double_scalarmult_vartime (r: ge_p2, a: StaticArray<u8>, A: ge_p3, b: StaticArray<u8>): void {
+       const Bi = base2
+       const aslide = ge_double_scalarmult_vartime_aslide
+       const bslide = ge_double_scalarmult_vartime_bslide
+       const Ai = ge_double_scalarmult_vartime_Ai /* A,3A,5A,7A,9A,11A,13A,15A */
+       const t = ge_double_scalarmult_vartime_t
+       const u = ge_double_scalarmult_vartime_u
+       const A2 = ge_double_scalarmult_vartime_A2
+       let i: i32 = 0
+
+       slide_vartime(aslide, a)
+       slide_vartime(bslide, b)
+
+       ge_p3_to_cached(Ai[0], A)
+
+       ge_p3_dbl(t, A)
+       ge_p1p1_to_p3(A2, t)
+
+       ge_add_cached(t, A2, Ai[0])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[1], u)
+
+       ge_add_cached(t, A2, Ai[1])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[2], u)
+
+       ge_add_cached(t, A2, Ai[2])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[3], u)
+
+       ge_add_cached(t, A2, Ai[3])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[4], u)
+
+       ge_add_cached(t, A2, Ai[4])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[5], u)
+
+       ge_add_cached(t, A2, Ai[5])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[6], u)
+
+       ge_add_cached(t, A2, Ai[6])
+       ge_p1p1_to_p3(u, t)
+       ge_p3_to_cached(Ai[7], u)
+
+       ge_p2_0(r)
+
+       for (i = 255; i >= 0; --i) {
+               if (aslide[i] || bslide[i]) {
+                       break
+               }
+       }
+
+       for (; i >= 0; --i) {
+               ge_p2_dbl(t, r)
+
+               if (aslide[i] > 0) {
+                       ge_p1p1_to_p3(u, t)
+                       ge_add_cached(t, u, Ai[aslide[i] / 2])
+               } else if (aslide[i] < 0) {
+                       ge_p1p1_to_p3(u, t)
+                       ge_sub_cached(t, u, Ai[(-aslide[i]) / 2])
+               }
+
+               if (bslide[i] > 0) {
+                       ge_p1p1_to_p3(u, t)
+                       ge_add_precomp(t, u, Bi[bslide[i] / 2])
+               } else if (bslide[i] < 0) {
+                       ge_p1p1_to_p3(u, t)
+                       ge_sub_precomp(t, u, Bi[(-bslide[i]) / 2])
+               }
+
+               ge_p1p1_to_p2(r, t)
+       }
+}
+
+function slide_vartime (r: StaticArray<i8>, a: StaticArray<u8>): void {
+       let i: i32
+       let b: i32
+       let k: i32
+       let ribs: i32
+       let cmp: i32
+
+       for (i = 0; i < 256; ++i) {
+               r[i] = 1 & (a[i >> 3] >> i8(i & 7))
+       }
+       for (i = 0; i < 256; ++i) {
+               if (!r[i]) {
+                       continue
+               }
+               for (b = 1; b <= 6 && i + b < 256; ++b) {
+                       if (!r[i + b]) {
+                               continue
+                       }
+                       ribs = r[i + b] << i8(b)
+                       cmp = r[i] + ribs
+                       if (cmp <= 15) {
+                               r[i] = i8(cmp)
+                               r[i + b] = 0
+                       } else {
+                               cmp = r[i] - ribs
+                               if (cmp < -15) {
+                                       break
+                               }
+                               r[i] = i8(cmp)
+                               for (k = i + b; k < 256; ++k) {
+                                       if (!r[k]) {
+                                               r[k] = 1
+                                               break
+                                       }
+                                       r[k] = 0
+                               }
+                       }
+               }
+       }
+}
+
+const ge_sub_cached_t0: FieldElement = fe()
+/**
+ * r = p - q
+ */
+function ge_sub_cached (r: ge_p1p1, p: ge_p3, q: ge_cached): void {
+       const t0 = ge_sub_cached_t0
+       fe_add(r.X, p.Y, p.X)
+       fe_sub(r.Y, p.Y, p.X)
+       fe_mul(r.Z, r.X, q.YminusX)
+       fe_mul(r.Y, r.Y, q.YplusX)
+       fe_mul(r.T, q.T2d, p.T)
+       fe_mul(r.X, p.Z, q.Z)
+       fe_add(t0, r.X, r.X)
+       fe_sub(r.X, r.Z, r.Y)
+       fe_add(r.Y, r.Z, r.Y)
+       fe_sub(r.Z, t0, r.T)
+       fe_add(r.T, t0, r.T)
+}
+
+const ge_sub_precomp_t0: FieldElement = fe()
+/**
+ * r = p - q
+ */
+function ge_sub_precomp (r: ge_p1p1, p: ge_p3, q: ge_precomp): void {
+       const t0 = ge_sub_precomp_t0
+       fe_add(r.X, p.Y, p.X)
+       fe_sub(r.Y, p.Y, p.X)
+       fe_mul(r.Z, r.X, q.yminusx)
+       fe_mul(r.Y, r.Y, q.yplusx)
+       fe_mul(r.T, q.xy2d, p.T)
+       fe_add(t0, p.Z, p.Z)
+       fe_sub(r.X, r.Z, r.Y)
+       fe_add(r.Y, r.Z, r.Y)
+       fe_sub(r.Z, t0, r.T)
+       fe_add(r.T, t0, r.T)
 }
index de259e72628803d90fb8077329aea5d332173433..6e2f75b8b7c30bcdee9d2d59e76c0ac6762ffadb 100644 (file)
@@ -2,8 +2,7 @@
 //! SPDX-License-Identifier: GPL-3.0-or-later\r
 \r
 import { Blake2b } from './blake2b'\r
-import { fe, fe_add, fe_copy, fe_cswap, fe_mul, fe_neg, fe_sq, fe_sub, FieldElement } from './fe'\r
-import { ge_is_canonical, ge_p3, ge_p3_0 } from './ge'\r
+import { ge_double_scalarmult_vartime, ge_frombytes, ge_frombytes_negate_vartime, ge_has_small_order, ge_is_canonical, ge_p2, ge_p2_to_p3, ge_p3, ge_p3_0, ge_p3_sub, ge_p3_tobytes, ge_scalarmult_base } from './ge'\r
 import { sc_is_canonical, sc_muladd, sc_reduce } from './sc'\r
 \r
 const BLOCKHASH_BYTES: i32 = 32\r
@@ -13,304 +12,10 @@ const SECRETKEY_BYTES: i32 = PRIVATEKEY_BYTES + PUBLICKEY_BYTES
 const SIGNATURE_BYTES: i32 = 64\r
 const SIGNEDBLOCKHASH_BYTES: i32 = SIGNATURE_BYTES + BLOCKHASH_BYTES\r
 \r
-const BASE: ge_p3 = {\r
-       X: fe([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]),\r
-       Y: fe([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]),\r
-       Z: fe([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),\r
-       T: fe([0xdd90, 0xa5b7, 0x8ab3, 0x6dde, 0x52f5, 0x7751, 0x9f80, 0x20f0, 0xe37d, 0x64ab, 0x4e8e, 0x66ea, 0x7665, 0xd78b, 0x5f0f, 0xe787]),\r
-       __brand: 'ge_p3'\r
-}\r
-const D: FieldElement = fe([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203])\r
-const D2: FieldElement = fe([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406])\r
-const I: FieldElement = fe([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83])\r
-\r
 // Static I/O buffers\r
 const INPUT_BUFFER = memory.data(128)\r
 const OUTPUT_BUFFER = memory.data(64)\r
 \r
-// a, b = 32 bytes\r
-//@ts-expect-error\r
-@inline\r
-function bitsdiff<T> (a: StaticArray<T>, b: StaticArray<T>): bool {\r
-       const ai = changetype<usize>(a)\r
-       const bi = changetype<usize>(b)\r
-       let d = load<i64>(ai) ^ load<i64>(bi)\r
-       d |= load<i64>(ai, 8) ^ load<i64>(bi, 8)\r
-       d |= load<i64>(ai, 16) ^ load<i64>(bi, 16)\r
-       d |= load<i64>(ai, 24) ^ load<i64>(bi, 24)\r
-       return d !== 0\r
-}\r
-\r
-// o, a = StaticArray<i32>[16]\r
-const pow_c: FieldElement = fe()\r
-function pow2523 (o: FieldElement, a: FieldElement): void {\r
-       const c = pow_c\r
-       fe_copy(c, a)\r
-\r
-       for (let i = 0; i < 249; i++) {\r
-               fe_sq(c, c)\r
-               fe_mul(c, c, a)\r
-       }\r
-       fe_sq(c, c)\r
-       fe_sq(c, c)\r
-       fe_mul(c, c, a)\r
-\r
-       fe_copy(o, c)\r
-}\r
-\r
-function car25519 (o: FieldElement): void {\r
-       let c: i32 = 0\r
-       for (let i = 0; i < 16; i++) {\r
-               c += o[i]\r
-               o[i] = c & 0xFFFF\r
-               c >>= 16\r
-       }\r
-       o[0] += 38 * c\r
-}\r
-\r
-const inv_c: FieldElement = fe()\r
-function inv25519 (o: FieldElement, a: FieldElement): void {\r
-       const c = inv_c\r
-       fe_copy(c, a)\r
-\r
-       for (let i = 0; i < 249; i++) {\r
-               fe_sq(c, c)\r
-               fe_mul(c, c, a)\r
-       }\r
-       fe_sq(c, c)\r
-       fe_sq(c, c)\r
-       fe_mul(c, c, a)\r
-       fe_sq(c, c)\r
-       fe_sq(c, c)\r
-       fe_mul(c, c, a)\r
-       fe_sq(c, c)\r
-       fe_mul(c, c, a)\r
-\r
-       fe_copy(o, c)\r
-}\r
-\r
-// a, b = StaticArray<i32>[16]\r
-const neq_c = new StaticArray<u8>(32)\r
-const neq_d = new StaticArray<u8>(32)\r
-function neq25519 (a: FieldElement, b: FieldElement): bool {\r
-       const c = neq_c\r
-       const d = neq_d\r
-       pack25519(c, a)\r
-       pack25519(d, b)\r
-       return bitsdiff(c, d)\r
-}\r
-\r
-const pack_m: FieldElement = fe()\r
-const pack_t: FieldElement = fe()\r
-function pack25519 (o: StaticArray<u8>, n: FieldElement): void {\r
-       const m = pack_m\r
-       const t = pack_t\r
-       let b: i32 = 0\r
-       memory.copy(changetype<usize>(t), changetype<usize>(n), 64)\r
-\r
-       car25519(t)\r
-       car25519(t)\r
-       car25519(t)\r
-\r
-       for (let j = 0; j < 2; j++) {\r
-               m[0] = t[0] - 0xffed\r
-               for (let i = 1; i < 15; i++) {\r
-                       m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1)\r
-                       m[i - 1] &= 0xffff\r
-               }\r
-               m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1)\r
-               b = (m[15] >> 16) & 1\r
-               m[14] &= 0xffff\r
-               fe_cswap(t, m, 1 - b)\r
-       }\r
-\r
-       for (let i = 0; i < 16; i++) {\r
-               o[(i << 1) + 0] = u8(t[i] & 0xff)\r
-               o[(i << 1) + 1] = u8(t[i] >> 8)\r
-       }\r
-}\r
-\r
-const par_d = new StaticArray<u8>(32)\r
-function par25519 (a: FieldElement): u8 {\r
-       const d = par_d\r
-       pack25519(d, a)\r
-       return d[0] & 1\r
-}\r
-\r
-function unpack25519 (o: FieldElement, n: StaticArray<u8>): void {\r
-       for (let i = 0; i < 16; i++) {\r
-               o[i] = i32(n[i << 1]) + (i32(n[(i << 1) + 1]) << 8)\r
-       }\r
-       o[15] &= (1 << 15) - 1\r
-}\r
-\r
-/**\r
- * Reusable buffers for point addition and doubling.\r
- */\r
-const a: FieldElement = fe()\r
-const b: FieldElement = fe()\r
-const c: FieldElement = fe()\r
-const d: FieldElement = fe()\r
-const e: FieldElement = fe()\r
-const f: FieldElement = fe()\r
-const g: FieldElement = fe()\r
-const h: FieldElement = fe()\r
-const t: FieldElement = fe()\r
-\r
-/**\r
- * Curve point addition in extended projective coordinates.\r
- * A = (Y1-X1)*(Y2-X2), B = (Y1+X1)*(Y2+X2), C = (T1*2*d)*T2, D = (Z1*2)*Z2\r
- * E = B-A, F = D-C, G = D+C, H = B+A\r
- * X3 = E*F, Y3 = G*H, Z3 = F*G, T3 = E*H\r
- */\r
-// p, q = StaticArray<i32>[4][16]\r
-function add (p: ge_p3, q: ge_p3): void {\r
-       fe_sub(a, p.Y, p.X)\r
-       fe_sub(t, q.Y, q.X)\r
-       fe_mul(a, a, t)\r
-       fe_add(b, p.X, p.Y)\r
-       fe_add(t, q.X, q.Y)\r
-       fe_mul(b, b, t)\r
-       fe_mul(c, p.T, q.T)\r
-       fe_mul(c, c, D2)\r
-       fe_mul(d, p.Z, q.Z)\r
-       fe_add(d, d, d)\r
-\r
-       fe_sub(e, b, a)\r
-       fe_sub(f, d, c)\r
-       fe_add(g, d, c)\r
-       fe_add(h, b, a)\r
-\r
-       fe_mul(p.X, e, f)\r
-       fe_mul(p.Y, h, g)\r
-       fe_mul(p.Z, g, f)\r
-       fe_mul(p.T, e, h)\r
-}\r
-\r
-/**\r
- * Doubles a curve point in projective coordinates.\r
- * A = X², B = Y², C = 2*Z², D = -A  (in mod p, negate is p-A; tweetnacl does 0-A)\r
- * E = (X+Y)²-A-B, G = D+B, F = G-C, H = D-B\r
- * X3 = E*F, Y3 = G*H, Z3 = F*G, T3 = E*H\r
- */\r
-function double (p: ge_p3): void {\r
-       fe_sq(a, p.X)\r
-       fe_sq(b, p.Y)\r
-       fe_sq(c, p.Z)\r
-       fe_add(c, c, c)\r
-       fe_neg(d, a)\r
-\r
-       fe_add(e, p.X, p.Y)\r
-       fe_sq(e, e)\r
-       fe_sub(e, e, a)\r
-       fe_sub(e, e, b)\r
-       fe_add(g, d, b)\r
-       fe_sub(f, g, c)\r
-       fe_sub(h, d, b)\r
-\r
-       fe_mul(p.X, e, f)\r
-       fe_mul(p.Y, g, h)\r
-       fe_mul(p.Z, f, g)\r
-       fe_mul(p.T, e, h)\r
-}\r
-\r
-// p, q = StaticArray<i32>[16]\r
-// offsets i32x16 = 64 bytes\r
-//@ts-expect-error\r
-@inline\r
-function cswap (p: ge_p3, q: ge_p3, b: i32): void {\r
-       fe_cswap(p.X, q.X, b)\r
-       fe_cswap(p.Y, q.Y, b)\r
-       fe_cswap(p.Z, q.Z, b)\r
-       fe_cswap(p.T, q.T, b)\r
-}\r
-\r
-const tx: FieldElement = fe()\r
-const ty: FieldElement = fe()\r
-const zi: FieldElement = fe()\r
-/**\r
- * Convert projective coordinates to a byte array.\r
- *\r
- * p = StaticArray<i32>[4][16] = (X,Y,Z,T) as 16 limbs each\r
-*/\r
-function pack (r: StaticArray<u8>, p: ge_p3): void {\r
-       inv25519(zi, p.Z)\r
-       fe_mul(tx, p.X, zi)\r
-       fe_mul(ty, p.Y, zi)\r
-       pack25519(r, ty)\r
-       r[31] ^= par25519(tx) << 7\r
-}\r
-\r
-// p, q = StaticArray<i32>[4][16]\r
-function scalarmult (p: ge_p3, q: ge_p3, s: StaticArray<u8>): void {\r
-       for (let i = 255; i >= 0; i--) {\r
-               const b: i32 = (s[i >> 3] >> u8(i & 7)) & 1\r
-               cswap(p, q, b)\r
-               add(q, p)\r
-               double(p)\r
-               cswap(p, q, b)\r
-       }\r
-}\r
-\r
-// p = StaticArray<i32>[4][16]\r
-const scalarbase_q = new ge_p3()\r
-function scalarbase (p: ge_p3, s: StaticArray<u8>): void {\r
-       const q = scalarbase_q\r
-       fe_copy(q.X, BASE.X)\r
-       fe_copy(q.Y, BASE.Y)\r
-       fe_copy(q.Z, BASE.Z)\r
-       fe_copy(q.T, BASE.T)\r
-       scalarmult(p, q, s)\r
-}\r
-\r
-const unpack_chk: FieldElement = fe()\r
-const unpack_num: FieldElement = fe()\r
-const unpack_den = new ge_p3()\r
-const unpack_t: FieldElement = fe()\r
-function unpackneg (r: ge_p3, p: StaticArray<u8>): i8 {\r
-       const chk = unpack_chk\r
-       const num = unpack_num\r
-       const den = unpack_den\r
-       const t = unpack_t\r
-\r
-       unpack25519(r.Y, p)\r
-       fe_sq(num, r.Y)\r
-       fe_mul(den.X, num, D)\r
-       fe_sub(num, num, r.Z)\r
-       fe_add(den.X, r.Z, den.X)\r
-\r
-       fe_sq(den.Y, den.X)\r
-       fe_sq(den.Z, den.Y)\r
-       fe_mul(den.T, den.Z, den.Y)\r
-       fe_mul(t, den.T, num)\r
-       fe_mul(t, t, den.X)\r
-\r
-       pow2523(t, t)\r
-       fe_mul(t, t, num)\r
-       fe_mul(t, t, den.X)\r
-       fe_mul(t, t, den.X)\r
-       fe_mul(r.X, t, den.X)\r
-\r
-       fe_sq(chk, r.X)\r
-       fe_mul(chk, chk, den.X)\r
-       if (neq25519(chk, num)) {\r
-               fe_mul(r.X, r.X, I)\r
-       }\r
-\r
-       fe_sq(chk, r.X)\r
-       fe_mul(chk, chk, den.X)\r
-\r
-       if (neq25519(chk, num)) {\r
-               return -1\r
-       }\r
-\r
-       if (par25519(r.X) === (p[31] >> 7)) {\r
-               fe_neg(r.X, r.X)\r
-       }\r
-       fe_mul(r.T, r.X, r.Y)\r
-       return 0\r
-}\r
-\r
 //@ts-expect-error\r
 @inline\r
 function clamp (k: StaticArray<u8>): void {\r
@@ -325,22 +30,19 @@ function crypto_hash (o: StaticArray<u8>, i: StaticArray<u8>): void {
 }\r
 \r
 const crypto_derive_A = new ge_p3()\r
-function crypto_derive (pk: StaticArray<u8>, sk: StaticArray<u8>): void {\r
+function crypto_derive (pk: StaticArray<u8>, sk: StaticArray<u8>, seed: StaticArray<u8>): void {\r
        const A = crypto_derive_A\r
        ge_p3_0(A)\r
 \r
-       crypto_hash(sk, sk)\r
+       crypto_hash(sk, seed)\r
        clamp(sk)\r
 \r
        trace(`seed hashed to sk: ${sk.map((b: u8) => b.toString(16).padStart(2, '0')).join('')}`)\r
 \r
-       scalarbase(A, sk)\r
-\r
-       trace(`sk scalarbase to A.X: ${A.X.map((b: i32) => b.toString(16).padStart(8, '0')).join('')}`)\r
-\r
-       pack(pk, A)\r
-\r
-       trace(`A packed to pk: ${pk.map((b: u8) => b.toString(16).padStart(2, '0')).join('')}`)\r
+       ge_scalarmult_base(A, sk)\r
+       ge_p3_tobytes(pk, A)\r
+       memory.copy(changetype<usize>(sk), changetype<usize>(seed), PRIVATEKEY_BYTES)\r
+       memory.copy(changetype<usize>(sk) + 32, changetype<usize>(pk), PUBLICKEY_BYTES)\r
 }\r
 \r
 const crypto_sign_az = new StaticArray<u8>(64)\r
@@ -370,8 +72,8 @@ function crypto_sign (s: StaticArray<u8>, m: StaticArray<u8>, sk: StaticArray<u8
        sc_reduce(nonce)\r
 \r
        // Compute R = rB\r
-       scalarbase(R, nonce)\r
-       pack(s, R)\r
+       ge_scalarmult_base(R, nonce)\r
+       ge_p3_tobytes(s, R)\r
 \r
        // Concatenate public key `A` and message `M`\r
        // from parameter arguments: A = sk[0,32], M = m\r
@@ -390,35 +92,39 @@ function crypto_sign (s: StaticArray<u8>, m: StaticArray<u8>, sk: StaticArray<u8
        nonce.fill(0)\r
 }\r
 \r
-const crypto_verify_open_hram = new StaticArray<u8>(64)\r
-const crypto_verify_open_p = new ge_p3()\r
-const crypto_verify_open_q = new ge_p3()\r
-const crypto_verify_open_t = new StaticArray<u8>(32)\r
-const crypto_verify_open_ram = new StaticArray<u8>(SIGNEDBLOCKHASH_BYTES)\r
-const crypto_verify_open_S = new StaticArray<u8>(32)\r
+const crypto_verify_h = new StaticArray<u8>(64)\r
+const crypto_verify_check = new ge_p3()\r
+const crypto_verify_expected_r = new ge_p3()\r
+const crypto_verify_A = new ge_p3()\r
+const crypto_verify_sb_ah = new ge_p3()\r
+const crypto_verify_sb_ah_p2 = new ge_p2()\r
+const crypto_verify_ram = new StaticArray<u8>(SIGNEDBLOCKHASH_BYTES)\r
+const crypto_verify_S = new StaticArray<u8>(32)\r
 /**\r
-* Verify signature `s` was made by signing message `m` using public key `pk`.\r
-*/\r
-function crypto_verify (s: StaticArray<u8>, m: StaticArray<u8>, pk: StaticArray<u8>): bool {\r
-       const hram = crypto_verify_open_hram\r
-       const p = crypto_verify_open_p\r
-       const q = crypto_verify_open_q\r
-       ge_p3_0(p)\r
-       ge_p3_0(q)\r
-\r
-       const t = crypto_verify_open_t\r
-       const ram = crypto_verify_open_ram\r
-       const S = crypto_verify_open_S\r
+ * Verify signature `s` was made by signing message `m` using public key `pk`.\r
+ */\r
+function crypto_verify (s: StaticArray<u8>, m: StaticArray<u8>, pk: StaticArray<u8>): i32 {\r
+       const h = crypto_verify_h\r
+       const check = crypto_verify_check\r
+       const expected_r = crypto_verify_expected_r\r
+       const A = crypto_verify_A\r
+       const sb_ah = crypto_verify_sb_ah\r
+       const sb_ah_p2 = crypto_verify_sb_ah_p2\r
+       const ram = crypto_verify_ram\r
+       const S = crypto_verify_S\r
 \r
        // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 <= k`)\r
-       if (!ge_is_canonical(pk)) return false\r
+       if (!ge_is_canonical(pk)) return -1\r
 \r
        // fail if private scalar `S` is non-canonical (`L <= S`)\r
        memory.copy(changetype<usize>(S), changetype<usize>(s) + 32, 32)\r
-       if (!sc_is_canonical(S)) return false\r
+       if (!sc_is_canonical(S)) return -1\r
+\r
+       if (ge_frombytes_negate_vartime(A, pk) != 0) return -1\r
+       if (ge_has_small_order(A) != 0) return -1\r
 \r
-       // fail\r
-       if (unpackneg(q, pk)) return false\r
+       if (ge_frombytes(expected_r, s) != 0) return -1\r
+       if (ge_has_small_order(expected_r) != 0) return -1\r
 \r
        // signature is nonce point R and scalar S (R || S)\r
        // data to hash is nonce point R, public key A, and message M\r
@@ -427,17 +133,14 @@ function crypto_verify (s: StaticArray<u8>, m: StaticArray<u8>, pk: StaticArray<
        memory.copy(changetype<usize>(ram), changetype<usize>(s), 32)\r
        memory.copy(changetype<usize>(ram) + 32, changetype<usize>(pk), 32)\r
        memory.copy(changetype<usize>(ram) + 64, changetype<usize>(m), 32)\r
-       crypto_hash(hram, ram)\r
-       sc_reduce(hram)\r
+       crypto_hash(h, ram)\r
+       sc_reduce(h)\r
 \r
-       scalarmult(p, q, hram)\r
-       ge_p3_0(q)\r
-       scalarbase(q, S)\r
-       add(p, q)\r
-       pack(t, p)\r
+       ge_double_scalarmult_vartime(sb_ah_p2, h, A, S)\r
+       ge_p2_to_p3(sb_ah, sb_ah_p2)\r
+       ge_p3_sub(check, expected_r, sb_ah)\r
 \r
-       // fail if any bit differs from encoded nonce point R, else pass\r
-       return !bitsdiff(s, t)\r
+       return ge_has_small_order(check) - 1\r
 }\r
 \r
 // Returns the pointer to the static input buffer (128 bytes).\r
@@ -451,21 +154,23 @@ export function getOutputPointer (): usize {
 }\r
 \r
 const derive_pk = new StaticArray<u8>(PUBLICKEY_BYTES)\r
-const derive_sk = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
+const derive_sk = new StaticArray<u8>(SECRETKEY_BYTES)\r
+const derive_seed = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
 /**\r
-* Derives a Nano public key from a private key and writes it to the static\r
-* output buffer. This mirrors the functionality of\r
-* `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property.\r
-*/\r
+ * Derives a Nano public key from a private key and writes it to the static\r
+ * output buffer. This mirrors the functionality of\r
+ * `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property.\r
+ */\r
 export function derive (): void {\r
        const pk = derive_pk\r
        const sk = derive_sk\r
+       const seed = derive_seed\r
        for (let i = 0; i < PRIVATEKEY_BYTES; i++) {\r
-               sk[i] = load<u8>(INPUT_BUFFER + i)\r
+               seed[i] = load<u8>(INPUT_BUFFER + i)\r
        }\r
 \r
        const start = performance.now()\r
-       crypto_derive(pk, sk)\r
+       crypto_derive(pk, sk, seed)\r
        const end = performance.now()\r
        trace('derive time', 1, end - start)\r
 \r
@@ -478,12 +183,12 @@ const sign_h = new StaticArray<u8>(BLOCKHASH_BYTES)
 const sign_sk = new StaticArray<u8>(SECRETKEY_BYTES)\r
 const sign_s = new StaticArray<u8>(SIGNEDBLOCKHASH_BYTES)\r
 /**\r
-* Signs the message using the private key and writes the signature to the static\r
-* output buffer. This mirrors the functionality of `nacl.sign.detached()`.\r
-*\r
-* @param {u64} h0-h3 - Message to sign (32 bytes as 4 × u64)\r
-* @param {u64} k0-k7 - Secret key (32 bytes private key + 32 bytes public key as 8 × u64)\r
-*/\r
+ * Signs the message using the private key and writes the signature to the static\r
+ * output buffer. This mirrors the functionality of `nacl.sign.detached()`.\r
+ *\r
+ * @param {u64} h0-h3 - Message to sign (32 bytes as 4 × u64)\r
+ * @param {u64} k0-k7 - Secret key (32 bytes private key + 32 bytes public key as 8 × u64)\r
+ */\r
 export function sign (): void {\r
        const h = sign_h\r
        const sk = sign_sk\r
@@ -511,15 +216,14 @@ const verify_h = new StaticArray<u8>(BLOCKHASH_BYTES)
 const verify_s = new StaticArray<u8>(SIGNATURE_BYTES)\r
 const verify_k = new StaticArray<u8>(PUBLICKEY_BYTES)\r
 /**\r
-* Verifies a signature on a block hash against a public key. This mirrors the\r
-* functionality of `nacl.sign.detached.verify()`.\r
-*\r
-* @param {u64} s0-s7 - Signature (64 bytes as 8 × u64)\r
-* @param {u64} h0-h3 - Blockhash (32 bytes as 4 × u64)\r
-* @param {u64} k0-k3 - Public key (32 bytes as 4 × u64)\r
-* @returns {i32} - 1 if valid, 0 if invalid\r
-*/\r
-export function verify (): i32 {\r
+ * Verifies a signature on a block hash against a public key. This mirrors the\r
+ * functionality of `nacl.sign.detached.verify()`.\r
+ *\r
+ * @param {u64} s0-s7 - Signature (64 bytes as 8 × u64)\r
+ * @param {u64} h0-h3 - Blockhash (32 bytes as 4 × u64)\r
+ * @param {u64} k0-k3 - Public key (32 bytes as 4 × u64)\r
+ */\r
+export function verify (): void {\r
        const s = verify_s\r
        const h = verify_h\r
        const k = verify_k\r
@@ -538,9 +242,9 @@ export function verify (): i32 {
        }\r
 \r
        const start = performance.now()\r
-       const result = crypto_verify(s, h, k)\r
+       const v = crypto_verify(s, h, k)\r
        const end = performance.now()\r
        trace('verify time', 1, end - start)\r
 \r
-       return result ? 1 : 0\r
+       store<u8>(OUTPUT_BUFFER, v)\r
 }\r
index bb38322d66c1f88e348e84198ed25744004f6e7c..ae85e4dec67453c5acc64aaa6fa32dce61384922 100644 (file)
@@ -4,7 +4,6 @@
 /**
  * Scalar operations modulo `L` where `L` is the group order of Curve25519.
  */
-
 import { load_3, load_4 } from './utils'
 
 /**
@@ -23,7 +22,7 @@ const L = StaticArray.fromArray<i32>([
  * - Reject immediately if `S >= 2²⁵³` since this implies `S >= L`
  * - Otherwise, check canonicity for `2²⁵² <= S < 2²⁵³`
  */
-export function sc_is_canonical (S: StaticArray<u8>): boolean {
+export function sc_is_canonical (S: StaticArray<u8>): bool {
        if ((S[31] & 0xF0) == 0) return true
        if ((S[31] & 0xE0) != 0) return false
 
index ab8fdc87c5b764842e489e8ce95b5bcf5321ae98..cb4029e2219fe7224ee174b70177e7eec14f19b5 100644 (file)
@@ -1,7 +1,7 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-const optblocker_u8: u8 = 0
+export const optblocker_u8: u8 = 0
 
 //@ts-expect-error
 @inline
@@ -31,7 +31,17 @@ export function load_4 (input: StaticArray<u8>, i: u8): u64 {
 
 //@ts-expect-error
 @inline
-export function negative (b: i8): i8 {
+export function negative (b: i8): u8 {
        const x: u8 = u8(b) /* 0..127: no 128..255: yes */
        return ((x >> 5) ^ optblocker_u8) >> 2 /* 1: yes; 0: no */
 }
+
+//@ts-expect-error
+@inline
+export function sodium_is_zero (n: StaticArray<u8>, nlen: i32): u8 {
+       let d: u8 = 0
+       for (let i: i32 = 0; i < nlen; i++) {
+               d |= n[i]
+       }
+       return 1 & ((d - 1) >> 8)
+}
index 37d601f45fa27018a4d08ae75684a01babd6b45d..fcfcd4c095ee4d01dc43c64351c8c0ff8780cb12 100644 (file)
@@ -209,13 +209,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
 
                                // PASS
                                result = await derive(TEST_VECTORS.privateKey)
-                               console.log(result)
+                               console.log('expected: ', TEST_VECTORS.publicKey.toUpperCase())
+                               console.log('actual', result?.toUpperCase?.())
                                result = result.toUpperCase() === TEST_VECTORS.publicKey.toUpperCase()
                                console.log(`derive() output for good private key is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
                                result = await sign(TEST_VECTORS.blockHash, TEST_VECTORS.privateKey, TEST_VECTORS.publicKey)
-                               console.log(result)
+                               console.log('expected: ', TEST_VECTORS.signature.toUpperCase())
+                               console.log('actual', result?.toUpperCase?.())
                                result = result.toUpperCase() === TEST_VECTORS.signature.toUpperCase()
                                console.log(`sign() output for good block hash and secret key is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
@@ -233,6 +235,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                                console.log(`verify() output for non-canonical signature is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
+                               result = await verify(TEST_VECTORS.signature, random(), TEST_VECTORS.publicKey)
+                               console.log(result)
+                               result = result === false
+                               console.log(`verify() output for random block hash is ${result === true ? 'correct' : 'incorrect'}`)
+                               expect.push(result)
+
                                result = await verify(TEST_VECTORS.signature, zeroes, TEST_VECTORS.publicKey)
                                console.log(result)
                                result = result === false
index b65b0a23ed3af17f78c319068f6bb02c07fe68cf..25e2f563a6ce08fb960f78907645e37c00763eab 100644 (file)
--- a/index.ts
+++ b/index.ts
@@ -24,7 +24,7 @@ const NanoNaCl = async (bytes: number[]): Promise<void> => {
        let memory: WebAssembly.Memory
        let derive: (k: Uint8Array) => Uint8Array<ArrayBuffer>
        let sign: (h: Uint8Array, k: Uint8Array) => Uint8Array<ArrayBuffer>
-       let verify: (s: Uint8Array, h: Uint8Array, k: Uint8Array) => boolean
+       let verify: (s: Uint8Array, h: Uint8Array, k: Uint8Array) => Uint8Array<ArrayBuffer>
 
        async function setup (): Promise<void> {
                try {
@@ -111,7 +111,7 @@ const NanoNaCl = async (bytes: number[]): Promise<void> => {
                                return s
                        }
 
-                       verify = function (s: Uint8Array, h: Uint8Array, k: Uint8Array): boolean {
+                       verify = function (s: Uint8Array, h: Uint8Array, k: Uint8Array): Uint8Array<ArrayBuffer> {
                                // assembly/nano-nacl/verify() => bool
                                let buffer: DataView | undefined = new DataView(memory.buffer)
                                let inPtr = exports.getInputPointer()
@@ -126,9 +126,12 @@ const NanoNaCl = async (bytes: number[]): Promise<void> => {
                                for (let i = 0; i < 32; i++) {
                                        buffer.setUint8(inPtr + i, k[i])
                                }
-                               const v = exports.verify()
+                               exports.verify()
+                               const outPtr = exports.getOutputPointer()
+                               const v = new Uint8Array(1)
+                               v[0] = buffer.getUint8(outPtr)
                                buffer = undefined
-                               return v === 1
+                               return v
                        }
 
                        isReady = true
@@ -190,11 +193,11 @@ const NanoNaCl = async (bytes: number[]): Promise<void> => {
                                        const signatureBytes = hex2bytes('signature', 64, signature)
                                        const blockHashBytes = hex2bytes('block hash', 32, blockHash)
                                        const publicKeyBytes = hex2bytes('public key', 32, publicKey)
-                                       const isVerified = verify(signatureBytes, blockHashBytes, publicKeyBytes)
-                                       if (isVerified == null) {
+                                       const verification = verify(signatureBytes, blockHashBytes, publicKeyBytes)
+                                       if (verification == null) {
                                                throw new TypeError('Invalid verification from WASM verify()')
                                        }
-                                       result = new Uint8Array([isVerified ? 1 : 0])
+                                       result = verification
                                }
                        }
                } catch (err: unknown) {
@@ -217,9 +220,8 @@ const NanoNaCl = async (bytes: number[]): Promise<void> => {
 const NanoNaClWorker = `;await (${NanoNaCl})([${nacl}])`
 
 /**
-* HOST CODE
-*/
-
+ * HOST CODE
+ */
 // Initialize CPU
 let isWorkerReady: boolean = false
 let worker: Worker
@@ -328,5 +330,5 @@ export async function sign (blockHash: string, secretKey: string): Promise<strin
 */
 export async function verify (signature: string, blockHash: string, publicKey: string): Promise<boolean> {
        const result = await run({ action: 'verify', signature, blockHash, publicKey })
-       return result === '01'
+       return result === '00'
 }