<commit_before><?php
/* Ported to PHP from Javascript by <NAME> 1/29/15
 * Ported to JavaScript from Java 07/01/14.
 *
 * Ported from C to Java by <NAME> [sahn0], 23/02/08.
 * Original: http://cds.xs4all.nl:8081/ecdh/
 */
/* Generic 64-bit integer implementation of Curve25519 ECDH
 * Written by <NAME>, 200608242056
 * Public domain.
 *
 * Based on work by <NAME>, http://cr.yp.to/ecdh.html
 */

class curve25519 {

    //region Constants

    $KEY_SIZE = 32;

    /* array length */
    $UNPACKED_SIZE = 16;

    /* group order (a prime near 2^252+2^124) */
    $ORDER = [
        237, 211, 245, 92,
        26, 99, 18, 88,
        214, 156, 247, 162,
        222, 249, 222, 20,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 16
    ];

    /* smallest multiple of the order that's >= 2^255 */
   $ORDER_TIMES_8 = [
        104, 159, 174, 231,
        210, 24, 147, 192,
        178, 230, 188, 23,
        245, 206, 247, 166,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 128
    ];

    /* constants 2Gy and 1/(2Gy) */
    $BASE_2Y = [
        22587, 610, 29883, 44076,
        15515, 9479, 25859, 56197,
        23910, 4462, 17831, 16322,
        62102, 36542, 52412, 16035
    ];

    $BASE_R2Y = [
        5744, 16384, 61977, 54121,
        8776, 18501, 26522, 34893,
        23833, 5823, 55924, 58749,
        24147, 14085, 13606, 6080
    ];

    $C1 = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    $C9 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    $C486671 = [0x6D0F, 0x0007, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    $C39420360 = [0x81C8, 0x0259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    $P25 = 33554431; /* (1 << 25) - 1 */
    $P26 = 67108863; /* (1 << 26) - 1 */

    //#endregion

    //region Key Agreement

    /* Private key clamping
     *   k [out] your private key for key agreement
     *   k  [in]  32 random bytes
     */
    function clamp ($k) {
        $k[31] &= 0x7F;
        $k[31] |= 0x40;
        $k[ 0] &= 0xF8;
        return $k;
    }

    //endregion

    //region radix 2^8 math

    function cpy32 ($d, $s) {
        for ($i = 0; $i < 32; $i++)
            $d[$i] = $s[$i];
    }

    /* p[m..n+m-1] = q[m..n+m-1] + z * x */
    /* n is the size of x */
    /* n+m is the size of p and q */
    function mula_small ($p, $q, $m, $x, $n, $z) {
        $m = $m | 0;
        $n = $n | 0;
        $z = $z | 0;

        $v = 0;
        for ($i = 0; $i < $n; ++$i) {
            $v += ($q[$i + $m] & 0xFF) + $z * ($x[$i] & 0xFF);
            $p[$i + $m] = ($v & 0xFF);
            $v >>= 8;
        }

        return $v;
    }

    /* p += x * y * z  where z is a small integer
     * x is size 32, y is size t, p is size 32+t
     * y is allowed to overlap with p+32 if you don't care about the upper half  */
    function mula32 ($p, $x, $y, $t, $z) {
        $t = $t | 0;
        $z = $z | 0;

        $n = 31;
        $w = 0;
        $i = 0;
        for (; $i < $t; $i++) {
            $zy = $z * ($y[$i] & 0xFF);
            $w += mula_small($p, $p, $i, $x, $n, $zy) + ($p[$i+$n] & 0xFF) + $zy * ($x[$n] & 0xFF);
            $p[$i + $n] = $w & 0xFF;
            $w >>= 8;
        }
        $p[$i + $n] = ($w + ($p[$i + $n] & 0xFF)) & 0xFF;
        return $w >> 8;
    }

    /* divide r (size n) by d (size t), returning quotient q and remainder r
     * quotient is size n-t+1, remainder is size t
     * requires t > 0 && d[t-1] !== 0
     * requires that r[-1] and d[-1] are valid memory locations
     * q may overlap with r+t */
    function divmod ($q, $r, $n, $d, $t) {
        $n = $n | 0;
        $t = $t | 0;

        $rn = 0;
        $dt = ($d[$t - 1] & 0xFF) << 8;
        if ($t > 1)
            $dt |= ($d[$t - 2] & 0xFF);

        while ($n-- >= $t) {
            $z = ($rn << 16) | (($r[$n] & 0xFF) << 8);
            if ($n > 0)
                $z |= ($r[$n - 1] & 0xFF);

            $i = $n - $t + 1;
            $z /= $dt;
            $rn += mula_small($r, $r, $i, $d, $t, -$z);
            $q[$i] = ($z + $rn) & 0xFF;
            /* rn is 0 or -1 (underflow) */
            mula_small($r, $r, $i, $d, $t, -$rn);
            $rn = $r[$n] & 0xFF;
            $r[$n] = 0;
        }

        $r[$t-1] = $rn & 0xFF;
    }

    function numsize ($x, $n) {
        while ($n-- !== 0 && $x[$n] === 0) { }
        return $n + 1;
    }

    /* Returns x if a contains the gcd, y if b.
     * Also, the returned buffer contains the inverse of a mod b,
     * as 32-byte signed.
     * x and y must have 64 bytes space for temporary use.
     * requires that a[-1] and b[-1] are valid memory locations  */
    function egcd32 ($x, $y, $a, $b) {
        $an, $bn = 32, $qn, $i;
        for ($i = 0; $i < 32; $i++)
            $x[$i] = $y[$i] = 0;
        $x[0] = 1;
        $an = numsize($a, 32);
        if ($an === 0)
            return $y; /* division by zero */
        $temp = array();
        while (true) {
            $qn = $bn - $an + 1;
            divmod($temp, $b, $bn, $a, $an);
            $bn = numsize($b, $bn);
            if ($bn === 0)
                return $x;
            mula32($y, $x, $temp, $qn, -1);

            $qn = $an - $bn + 1;
            divmod($temp, $a, $an, $b, $bn);
            $an = numsize($a, $an);
            if ($an === 0)
                return $y;
            mula32($x, $y, $temp, $qn, -1);
        }
    }

    //endregion

    //region radix 2^25.5 GF(2^255-19) math

    //region pack / unpack

    /* Convert to internal format from little-endian byte format */
    function unpack ($x, $m) {
        for ($i = 0; $i < $KEY_SIZE; $i += 2)
            $x[$i / 2] = $m[$i] & 0xFF | (($m[$i + 1] & 0xFF) << 8);
    }

    /* Check if reduced-form input >= 2^255-19 */
    function is_overflow ($x) {
        return (
            (($x[0] > $P26 - 19)) &&
                (($x[1] & $x[3] & $x[5] & $x[7] & $x[9]) === $P25) &&
                (($x[2] & $x[4] & $x[6] & $x[8]) === $P26)
            ) || ($x[9] > $P25);
    }

    /* Convert from internal format to little-endian byte format.  The
     * number must be in a reduced form which is output by the following ops:
     *     unpack, mul, sqr
     *     set --  if input in range 0 .. P25
     * If you're unsure if the number is reduced, first multiply it by 1.  */
    function pack ($x, $m) {
        for ($i = 0; $i < $UNPACKED_SIZE; ++$i) {
            $m[2 * $i] = $x[$i] & 0x00FF;
            $m[2 * $i + 1] = ($x[$i] & 0xFF00) >> 8;
        }
    }

    //endregion

    function createUnpackedArray () {
        return new Uint16Array($UNPACKED_SIZE);
    }

    /* Copy a number */
    function cpy ($d, $s) {
        for ($i = 0; $i < $UNPACKED_SIZE; ++$i)
            $d[$i] = $s[$i];
    }

    /* Set a number to value, which must be in range -185861411 .. 185861411 */
    function set ($d, $s) {
        $d[0] = $s;
        for (var $i = 1; $i < $UNPACKED_SIZE; ++$i)
            $d[$i] = 0;
    }


    // shortcuts from standard curve25519
    private $zero = [0,0,0,0, 0,0,0,0, 0,0];
    private $one  = [1,0,0,0, 0,0,0,0, 0,0];
    private $nine = [9,0,0,0, 0,0,0,0, 0,0];
    function add($a, $b) {
        return [
            $a[0] + $b[0], $a[1] + $b[1], $a[2] + $b[2], $a[3] + $b[3], $a[4] + $b[4],
            $a[5] + $b[5], $a[6] + $b[6], $a[7] + $b[7], $a[8] + $b[8], $a[9] + $b[9]
        ];
    }
    function sub($a, $b) {
        $r = [
            ($c = 0x7ffffda + $a[0] - $b[0]             ) & 0x3ffffff,
            ($c = 0x3fffffe + $a[1] - $b[1] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[2] - $b[2] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[3] - $b[3] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[4] - $b[4] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[5] - $b[5] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[6] - $b[6] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[7] - $b[7] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[8] - $b[8] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[9] - $b[9] + ($c >> 26)) & 0x1ffffff,
        ];
        $r[0] += 19 * ($c >> 25);
        return $r;
    }
    function mul($a, $b) {
        list($s0, $s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) = $a;
        list($r0, $r1, $r2, $r3, $r4, $r5, $r6, $r7, $r8, $r9) = $b;
        $r1_2 = $r1 * 2;
        $r3_2 = $r3 * 2;
        $r5_2 = $r5 * 2;
        $r2_19 = $r2 * 19;
        $r4_19 = $r4 * 19;
        $r5_19 = $r5 * 19;
        $r6_19 = $r6 * 19;
        $r7_19 = $r7 * 19;
        $r8_19 = $r8 * 19;
        $r9_19 = $r9 * 19;
        $r3_38 = $r3 * 38;
        $r5_38 = $r5 * 38;
        $r7_38 = $r7 * 38;
        $r9_38 = $r9 * 38;
        $r = [
            ($c = $m0 = ($r0 * $s0) + ($r1 * 38 * $s9) + ($r2_19 * $s8) + ($r3_38   * $s7) + ($r4_19 * $s6) + ($r5_38 * $s5) + ($r6_19 * $s4) + ($r7_38  * $s3) + ($r8_19 * $s2) + ($r9_38 * $s1)             ) & 0x3ffffff,
            ($c = $m1 = ($r0 * $s1) + ($r1      * $s0) + ($r2_19 * $s9) + ($r3 * 19 * $s8) + ($r4_19 * $s7) + ($r5_19 * $s6) + ($r6_19 * $s5) + ($r7_19  * $s4) + ($r8_19 * $s3) + ($r9_19 * $s2) + ($c >> 26)) & 0x1ffffff,
            ($c = $m2 = ($r0 * $s2) + ($r1_2    * $s1) + ($r2    * $s0) + ($r3_38   * $s9) + ($r4_19 * $s8) + ($r5_38 * $s7) + ($r6_19 * $s6) + ($r7_38  * $s5) + ($r8_19 * $s4) + ($r9_38 * $s3) + ($c >> 25)) & 0x3ffffff,
            ($c = $m3 = ($r0 * $s3) + ($r1      * $s2) + ($r2    * $s1) + ($r3      * $s0) + ($r4_19 * $s9) + ($r5_19 * $s8) + ($r6_19 * $s7) + ($r7_19  * $s6) + ($r8_19 * $s5) + ($r9_19 * $s4) + ($c >> 26)) & 0x1ffffff,
            ($c = $m4 = ($r0 * $s4) + ($r1_2    * $s3) + ($r2    * $s2) + ($r3_2    * $s1) + ($r4    * $s0) + ($r5_38 * $s9) + ($r6_19 * $s8) + ($r7_38  * $s7) + ($r8_19 * $s6) + ($r9_38 * $s5) + ($c >> 25)) & 0x3ffffff,
            ($c = $m5 = ($r0 * $s5) + ($r1      * $s4) + ($r2    * $s3) + ($r3      * $s2) + ($r4    * $s1) + ($r5    * $s0) + ($r6_19 * $s9) + ($r7_19  * $s8) + ($r8_19 * $s7) + ($r9_19 * $s6) + ($c >> 26)) & 0x1ffffff,
            ($c = $m6 = ($r0 * $s6) + ($r1_2    * $s5) + ($r2    * $s4) + ($r3_2    * $s3) + ($r4    * $s2) + ($r5_2  * $s1) + ($r6    * $s0) + ($r7_38  * $s9) + ($r8_19 * $s8) + ($r9_38 * $s7) + ($c >> 25)) & 0x3ffffff,
            ($c = $m7 = ($r0 * $s7) + ($r1      * $s6) + ($r2    * $s5) + ($r3      * $s4) + ($r4    * $s3) + ($r5    * $s2) + ($r6    * $s1) + ($r7     * $s0) + ($r8_19 * $s9) + ($r9_19 * $s8) + ($c >> 26)) & 0x1ffffff,
            ($c = $m8 = ($r0 * $s8) + ($r1_2    * $s7) + ($r2    * $s6) + ($r3_2    * $s5) + ($r4    * $s4) + ($r5_2  * $s3) + ($r6    * $s2) + ($r7 * 2 * $s1) + ($r8    * $s0) + ($r9_38 * $s9) + ($c >> 25)) & 0x3ffffff,
            ($c = $m9 = ($r0 * $s9) + ($r1      * $s8) + ($r2    * $s7) + ($r3      * $s6) + ($r4    * $s5) + ($r5    * $s4) + ($r6    * $s3) + ($r7     * $s2) + ($r8    * $s1) + ($r9    * $s0) + ($c >> 26)) & 0x1ffffff
        ];
        $r[0] += ($c = 19 * ($c >> 25)) & 0x3ffffff;
        $r[1] +=            ($c >> 26);
        return $r;
    }
    function sqr($a, $n = 1) {
        list($r0, $r1, $r2, $r3, $r4, $r5, $r6, $r7, $r8, $r9) = $a;
        do {
            $r0_2 = $r0 * 2;
            $r1_2 = $r1 * 2;
            $r2_2 = $r2 * 2;
            $r3_2 = $r3 * 2;
            $r4_2 = $r4 * 2;
            $r5_2 = $r5 * 2;
            $r7_2 = $r7 * 2;
            $r6_19 = $r6 * 19;
            $r7_38 = $r7 * 38;
            $r8_19 = $r8 * 19;
            $r9_38 = $r9 * 38;
            $s0 = ($r0   * $r0) + ($r5 * $r5 * 38) + ($r6_19 * $r4_2) + ($r7_38 * $r3_2) + ($r8_19 * $r2_2) + ($r9_38 * $r1_2);
            $s1 = ($r0_2 * $r1)                    + ($r6_19 * $r5_2) + ($r7_38 * $r4  ) + ($r8_19 * $r3_2) + ($r9_38 * $r2  );
            $s2 = ($r0_2 * $r2) + ($r1_2 * $r1)    + ($r6_19 * $r6  ) + ($r7_38 * $r5_2) + ($r8_19 * $r4_2) + ($r9_38 * $r3_2);
            $s3 = ($r0_2 * $r3) + ($r1_2 * $r2)                       + ($r7_38 * $r6  ) + ($r8_19 * $r5_2) + ($r9_38 * $r4  );
            $s4 = ($r0_2 * $r4) + ($r1_2 * $r3_2) + ($r2   * $r2)     + ($r7_38 * $r7  ) + ($r8_19 * $r6*2) + ($r9_38 * $r5_2);
            $s5 = ($r0_2 * $r5) + ($r1_2 * $r4  ) + ($r2_2 * $r3)                        + ($r8_19 * $r7_2) + ($r9_38 * $r6  );
            $s6 = ($r0_2 * $r6) + ($r1_2 * $r5_2) + ($r2_2 * $r4) + ($r3_2 * $r3)        + ($r8_19 * $r8  ) + ($r9_38 * $r7_2);
            $s7 = ($r0_2 * $r7) + ($r1_2 * $r6  ) + ($r2_2 * $r5) + ($r3_2 * $r4  )                         + ($r9_38 * $r8  );
            $s8 = ($r0_2 * $r8) + ($r1_2 * $r7_2) + ($r2_2 * $r6) + ($r3_2 * $r5_2) + ($r4 * $r4  )         + ($r9_38 * $r9  );
            $s9 = ($r0_2 * $r9) + ($r1_2 * $r8  ) + ($r2_2 * $r7) + ($r3_2 * $r6  ) + ($r4 * $r5_2);
            $r0 = ($c = $s0             ) & 0x3ffffff;
            $r1 = ($c = $s1 + ($c >> 26)) & 0x1ffffff;
            $r2 = ($c = $s2 + ($c >> 25)) & 0x3ffffff;
            $r3 = ($c = $s3 + ($c >> 26)) & 0x1ffffff;
            $r4 = ($c = $s4 + ($c >> 25)) & 0x3ffffff;
            $r5 = ($c = $s5 + ($c >> 26)) & 0x1ffffff;
            $r6 = ($c = $s6 + ($c >> 25)) & 0x3ffffff;
            $r7 = ($c = $s7 + ($c >> 26)) & 0x1ffffff;
            $r8 = ($c = $s8 + ($c >> 25)) & 0x3ffffff;
            $r9 = ($c = $s9 + ($c >> 26)) & 0x1ffffff;
            $r0 += ($c = 19 * ($c >> 25)) & 0x3ffffff;
            $r1 +=            ($c >> 26);
        } while (--$n);
        return [$r0, $r1, $r2, $r3, $r4, $r5, $r6, $r7, $r8, $r9];
    }
    function mul121665($in) {
        $r = [
            ($c = $in[0] * 121665             ) & 0x3ffffff,
            ($c = $in[1] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[2] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[3] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[4] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[5] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[6] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[7] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[8] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[9] * 121665 + ($c >> 26)) & 0x1ffffff,
        ];
        $r[0] += 19 * ($c >> 25);
        return $r;
    }
    function scalarmult($f, $c) {
        $t = $this->one;
        $u = $this->zero;
        $v = $this->one;
        $w = $c;
        $swapBit = 1;
        $i = 254;
        while ($i --> 2) {
            $x = $this->add($w, $v);
            $v = $this->sub($w, $v);
            $y = $this->add($t, $u);
            $u = $this->sub($t, $u);
            $t = $this->mul($y, $v);
            $u = $this->mul($x, $u);
            $z = $this->add($t, $u);
            $u = $this->sqr($this->sub($t, $u));
            $t = $this->sqr($z);
            $u = $this->mul($u, $c);
            $x = $this->sqr($x);
            $v = $this->sqr($v);
            $w = $this->mul($x, $v);
            $v = $this->sub($x, $v);
            $v = $this->mul($v, $this->add($this->mul121665($v), $x));
            $b = ($f[$i >> 3] >> ($i & 7)) & 1;
            $swap = $b ^ $swapBit;
            list($w, $t) = [[$w, $t], [$t, $w]][$swap];
            list($v, $u) = [[$v, $u], [$u, $v]][$swap];
            $swapBit = $b;
        }
        $i = 3;
        while ($i--) {
            $x = $this->sqr($this->add($w, $v));
            $v = $this->sqr($this->sub($w, $v));
            $w = $this->mul($x, $v);
            $v = $this->sub($x, $v);
            $v = $this->mul($v, $this->add($this->mul121665($v), $x));
        }
        $a = $this->sqr($v);
        $b = $this->mul($this->sqr($a, 2), $v);
        $a = $this->mul($b, $a);
        $b = $this->mul($this->sqr($a), $b);
        $b = $this->mul($this->sqr($b, 5), $b);
        $c = $this->mul($this->sqr($b, 10), $b);
        $b = $this->mul($this->sqr($this->mul($this->sqr($c, 20), $c), 10), $b);
        $c = $this->mul($this->sqr($b, 50), $b);
        $r = $this->mul($w, $this->mul($this->sqr($this->mul($this->sqr($this->mul($this->sqr($c, 100), $c), 50), $b), 5), $a));
        $r = [
            ($c = $r[0] + 0x4000000             ) & 0x3ffffff,
            ($c = $r[1] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[2] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[3] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[4] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[5] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[6] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[7] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[8] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[9] + 0x1ffffff + ($c >> 26)) & 0x1ffffff
        ];
        return pack('V8',
             $r[0]        | ($r[1] << 26),
            ($r[1] >>  6) | ($r[2] << 19),
            ($r[2] >> 13) | ($r[3] << 13),
            ($r[3] >> 19) | ($r[4] <<  6),
             $r[5]        | ($r[6] << 25),
            ($r[6] >>  7) | ($r[7] << 19),
            ($r[7] >> 13) | ($r[8] << 12),
            ($r[8] >> 20) | ($r[9] <<  6)
        );
    }
    function clamp($secret)
    {
        $e = array_values(unpack('C32', $secret));
        $e[0]  &= 0xf8;
        $e[31] &= 0x7f;
        $e[31] |= 0x40;
        return $e;
    }
    function getPublic($secret)
    {
        if (!is_string($secret) || strlen($secret) !== 32) {
            throw new InvalidArgumentException('Secret must be a 32 byte string');
        }
        return $this->scalarmult($this->clamp($secret), $this->nine);
    }
    function getShared($secret, $public)
    {
        if (!is_string($secret) || strlen($secret) !== 32) {
            throw new InvalidArgumentException('Secret must be a 32 byte string');
        }
        if (!is_string($public) || strlen($public) !== 32) {
            throw new InvalidArgumentException('Public must be a 32 byte string');
        }
        $w = unpack('V8', $public);
        $r = [
              $w[1]                         & 0x3ffffff, // 26
            (($w[1] >> 26) | ($w[2] <<  6)) & 0x1ffffff, // 25 - 51
            (($w[2] >> 19) | ($w[3] << 13)) & 0x3ffffff, // 26 - 77
            (($w[3] >> 13) | ($w[4] << 19)) & 0x1ffffff, // 25 - 102
             ($w[4] >>  6)                  & 0x3ffffff, // 26 - 128
              $w[5]                         & 0x1ffffff, // 25 - 153
            (($w[5] >> 25) | ($w[6] <<  7)) & 0x3ffffff, // 26 - 179
            (($w[6] >> 19) | ($w[7] << 13)) & 0x1ffffff, // 25 - 204
            (($w[7] >> 12) | ($w[8] << 20)) & 0x3ffffff, // 26 - 230
             ($w[8] >> 6)                   & 0x1ffffff, // 25 - 255
        ];
        return $this->scalarmult($this->clamp($secret), $r);
    }


    /* Add/subtract two numbers.  The inputs must be in reduced form, and the
     * output isn't, so to do another addition or subtraction on the output,
     * first multiply it by one to reduce it. */
    //$add = $c255laddmodp;
    //$sub = $c255lsubmodp;

    /* Multiply a number by a small integer in range -185861411 .. 185861411.
     * The output is in reduced form, the input x need not be.  x and xy may point
     * to the same buffer. */
    $mul_small = $c255lmulasmall;

    /* Multiply two numbers.  The output is in reduced form, the inputs need not be. */
    $mul = $c255lmulmodp;

    /* Square a number.  Optimization of  mul25519(x2, x, x)  */
    $sqr = $c255lsqrmodp;

    /* Calculates a reciprocal.  The output is in reduced form, the inputs need not
     * be.  Simply calculates  y = x^(p-2)  so it's not too fast. */
    /* When sqrtassist is true, it instead calculates y = x^((p-5)/8) */
    function recip ($y, $x, $sqrtassist) {
        $t0 = createUnpackedArray();
        $t1 = createUnpackedArray();
        $t2 = createUnpackedArray();
        $t3 = createUnpackedArray();
        $t4 = createUnpackedArray();

        /* the chain for x^(2^255-21) is straight from djb's implementation */
        $i;
        sqr($t1, $x); /*  2 === 2 * 1 */
        sqr($t2, $t1); /*  4 === 2 * 2    */
        sqr($t0, $t2); /*  8 === 2 * 4    */
        mul($t2, $t0, $x); /*  9 === 8 + 1 */
        mul($t0, $t2, $t1); /* 11 === 9 + 2    */
        sqr($t1, $t0); /* 22 === 2 * 11   */
        mul($t3, $t1, $t2); /* 31 === 22 + 9 === 2^5   - 2^0   */
        sqr($t1, $t3); /* 2^6   - 2^1 */
        sqr($t2, $t1); /* 2^7   - 2^2 */
        sqr($t1, $t2); /* 2^8   - 2^3 */
        sqr($t2, $t1); /* 2^9   - 2^4 */
        sqr($t1, $t2); /* 2^10  - 2^5 */
        mul($t2, $t1, $t3); /* 2^10  - 2^0 */
        sqr($t1, $t2); /* 2^11  - 2^1 */
        sqr($t3, $t1); /* 2^12  - 2^2 */
        for ($i = 1; $i < 5; $i++) {
            sqr($t1, $t3);
            sqr($t3, $t1);
        } /* t3 */ /* 2^20  - 2^10  */
        mul($t1, $t3, $t2); /* 2^20  - 2^0 */
        sqr($t3, $t1); /* 2^21  - 2^1 */
        sqr($t4, $t3); /* 2^22  - 2^2 */
        for ($i = 1; $i < 10; $i++) {
            sqr($t3, $t4);
            sqr($t4, $t3);
        } /* t4 */ /* 2^40  - 2^20  */
        mul($t3, $t4, $t1); /* 2^40  - 2^0 */
        for ($i = 0; $i < 5; $i++) {
            sqr($t1, $t3);
            sqr($t3, $t1);
        } /* t3 */ /* 2^50  - 2^10  */
        mul($t1, $t3, $t2); /* 2^50  - 2^0 */
        sqr($t2, $t1); /* 2^51  - 2^1 */
        sqr($t3, $t2); /* 2^52  - 2^2 */
        for ($i = 1; $i < 25; $i++) {
            sqr($t2, $t3);
            sqr($t3, $t2);
        } /* t3 */ /* 2^100 - 2^50 */
        mul($t2, $t3, $t1); /* 2^100 - 2^0 */
        sqr($t3, $t2); /* 2^101 - 2^1 */
        sqr($t4, $t3); /* 2^102 - 2^2 */
        for ($i = 1; $i < 50; $i++) {
            sqr($t3, $t4);
            sqr($t4, $t3);
        } /* t4 */ /* 2^200 - 2^100 */
        mul($t3, $t4, $t2); /* 2^200 - 2^0 */
        for ($i = 0; $i < 25; $i++) {
            sqr($t4, $t3);
            sqr($t3, $t4);
        } /* t3 */ /* 2^250 - 2^50  */
        mul($t2, $t3, $t1); /* 2^250 - 2^0 */
        sqr($t1, $t2); /* 2^251 - 2^1 */
        sqr($t2, $t1); /* 2^252 - 2^2 */
        if ($sqrtassist !== 0) {
            mul($y, $x, $t2); /* 2^252 - 3 */
        } else {
            sqr($t1, $t2); /* 2^253 - 2^3 */
            sqr($t2, $t1); /* 2^254 - 2^4 */
            sqr($t1, $t2); /* 2^255 - 2^5 */
            mul($y, $t1, $t0); /* 2^255 - 21   */
        }
    }

    /* checks if x is "negative", requires reduced input */
    function is_negative ($x) {
        $isOverflowOrNegative = is_overflow($x) || $x[9] < 0;
        $leastSignificantBit = $x[0] & 1;
        return (($isOverflowOrNegative ? 1 : 0) ^ $leastSignificantBit) & 0xFFFFFFFF;
    }

    /* a square root */
    function sqrt ($x, $u) {
        $v = createUnpackedArray();
        $t1 = createUnpackedArray();
        $t2 = createUnpackedArray();

        add($t1, $u, $u); /* t1 = 2u       */
        recip($v, $t1, 1); /* v = (2u)^((p-5)/8)  */
        sqr($x, $v); /* x = v^2       */
        mul($t2, $t1, $x); /* t2 = 2uv^2       */
        sub($t2, $t2, $C1); /* t2 = 2uv^2-1        */
        mul($t1, $v, $t2); /* t1 = v(2uv^2-1)  */
        mul($x, $u, $t1); /* x = uv(2uv^2-1)   */
    }

    //endregion

    //region JavaScript Fast Math

    function c255lsqr8h ($a7, $a6, $a5, $a4, $a3, $a2, $a1, $a0) {
        $r = array();
        $v;
        $r[0] = ($v = $a0*$a0) & 0xFFFF;
        $r[1] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a1) & 0xFFFF;
        $r[2] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a2 + $a1*$a1) & 0xFFFF;
        $r[3] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a3 + 2*$a1*$a2) & 0xFFFF;
        $r[4] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a4 + 2*$a1*$a3 + $a2*$a2) & 0xFFFF;
        $r[5] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a5 + 2*$a1*$a4 + 2*$a2*$a3) & 0xFFFF;
        $r[6] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a6 + 2*$a1*$a5 + 2*$a2*$a4 + $a3*$a3) & 0xFFFF;
       $r[7] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a7 + 2*$a1*$a6 + 2*$a2*$a5 + 2*$a3*$a4) & 0xFFFF;
        $r[8] = ($v = (($v / 0x10000) | 0) + 2*$a1*$a7 + 2*$a2*$a6 + 2*$a3*$a5 + $a4*$a4) & 0xFFFF;
        $r[9] = ($v = (($v / 0x10000) | 0) + 2*$a2*a7 + 2*$a3*$a6 + 2*$a4*$a5) & 0xFFFF;
        $r[10] = ($v = (($v / 0x10000) | 0) + 2*$a3*a7 + 2*$a4*$a6 + $a5*$a5) & 0xFFFF;
        $r[11] = ($v = (($v / 0x10000) | 0) + 2*$a4*a7 + 2*$a5*$a6) & 0xFFFF;
        $r[12] = ($v = (($v / 0x10000) | 0) + 2*$a5*a7 + $a6*$a6) & 0xFFFF;
        $r[13] = ($v = (($v / 0x10000) | 0) + 2*$a6*a7) & 0xFFFF;
        $r[14] = ($v = (($v / 0x10000) | 0) + $a7*$a7) & 0xFFFF;
        $r[15] = (($v / 0x10000) | 0);
        return $r;
    }

    function c255lsqrmodp ($r, $a) {
        $x = c255lsqr8h($a[15], $a[14], $a[13], $a[12], $a[11], $a[10], $a[9], $a[8]);
        $z = c255lsqr8h($a[7], $a[6], $a[5], $a[4], $a[3], $a[2], $a[1], $a[0]);
        $y = c255lsqr8h($a[15] + $a[7], $a[14] + $a[6], $a[13] + $a[5], $a[12] + $a[4], $a[11] + $a[3], $a[10] + $a[2], $a[9] + $a[1], $a[8] + $a[0]);

        $v;
        $r[0] = ($v = 0x800000 + $z[0] + ($y[8] -$x[8] -$z[8] + $x[0] -0x80) * 38) & 0xFFFF;
        $r[1] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[1] + ($y[9] -$x[9] -$z[9] + $x[1]) * 38) & 0xFFFF;
        $r[2] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[2] + ($y[10] -$x[10] -$z[10] + $x[2]) * 38) & 0xFFFF;
        $r[3] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[3] + ($y[11] -$x[11] -$z[11] + $x[3]) * 38) & 0xFFFF;
        $r[4] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[4] + ($y[12] -$x[12] -$z[12] + $x[4]) * 38) & 0xFFFF;
        $r[5] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[5] + ($y[13] -$x[13] -$z[13] + $x[5]) * 38) & 0xFFFF;
        $r[6] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[6] + ($y[14] -$x[14] -$z[14] + $x[6]) * 38) & 0xFFFF;
        $r[7] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[7] + ($y[15] -$x[15] -$z[15] + $x[7]) * 38) & 0xFFFF;
        $r[8] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[8] + $y[0] -$x[0] -$z[0] + $x[8] * 38) & 0xFFFF;
        $r[9] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[9] + $y[1] -$x[1] -$z[1] + $x[9] * 38) & 0xFFFF;
        $r[10] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[10] + $y[2] -$x[2] -$z[2] + $x[10] * 38) & 0xFFFF;
        $r[11] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[11] + $y[3] -$x[3] -$z[3] + $x[11] * 38) & 0xFFFF;
        $r[12] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[12] + $y[4] -$x[4] -$z[4] + $x[12] * 38) & 0xFFFF;
        $r[13] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[13] + $y[5] -$x[5] -$z[5] + $x[13] * 38) & 0xFFFF;
        $r[14] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[14] + $y[6] -$x[6] -$z[6] + $x[14] * 38) & 0xFFFF;
        $r15 = 0x7fff80 + (($v / 0x10000) | 0) + $z[15] + $y[7] -$x[7] -$z[7] + $x[15] * 38;
        c255lreduce($r, $r15);
    }

    function c255lmul8h ($a7, $a6, $a5, $a4, $a3, $a2, $a1, $a0, $b7, $b6, $b5, $b4, $b3, $b2, $b1, $b0) {
        $r = [];
        $v;
        r[0] = (v = a0*b0) & 0xFFFF;
        r[1] = (v = ((v / 0x10000) | 0) + a0*b1 + a1*b0) & 0xFFFF;
        r[2] = (v = ((v / 0x10000) | 0) + a0*b2 + a1*b1 + a2*b0) & 0xFFFF;
        r[3] = (v = ((v / 0x10000) | 0) + a0*b3 + a1*b2 + a2*b1 + a3*b0) & 0xFFFF;
        r[4] = (v = ((v / 0x10000) | 0) + a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0) & 0xFFFF;
        r[5] = (v = ((v / 0x10000) | 0) + a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0) & 0xFFFF;
        r[6] = (v = ((v / 0x10000) | 0) + a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0) & 0xFFFF;
        r[7] = (v = ((v / 0x10000) | 0) + a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0) & 0xFFFF;
        r[8] = (v = ((v / 0x10000) | 0) + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1) & 0xFFFF;
        r[9] = (v = ((v / 0x10000) | 0) + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2) & 0xFFFF;
        r[10] = (v = ((v / 0x10000) | 0) + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3) & 0xFFFF;
        r[11] = (v = ((v / 0x10000) | 0) + a4*b7 + a5*b6 + a6*b5 + a7*b4) & 0xFFFF;
        r[12] = (v = ((v / 0x10000) | 0) + a5*b7 + a6*b6 + a7*b5) & 0xFFFF;
        r[13] = (v = ((v / 0x10000) | 0) + a6*b7 + a7*b6) & 0xFFFF;
        r[14] = (v = ((v / 0x10000) | 0) + a7*b7) & 0xFFFF;
        r[15] = ((v / 0x10000) | 0);
        return r;
    }

    function c255lmulmodp (r, a, b) {
        // Karatsuba multiplication scheme: x*y = (b^2+b)*x1*y1 - b*(x1-x0)*(y1-y0) + (b+1)*x0*y0
        var x = c255lmul8h(a[15], a[14], a[13], a[12], a[11], a[10], a[9], a[8], b[15], b[14], b[13], b[12], b[11], b[10], b[9], b[8]);
        var z = c255lmul8h(a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0], b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0]);
        var y = c255lmul8h(a[15] + a[7], a[14] + a[6], a[13] + a[5], a[12] + a[4], a[11] + a[3], a[10] + a[2], a[9] + a[1], a[8] + a[0],
            b[15] + b[7], b[14] + b[6], b[13] + b[5], b[12] + b[4], b[11] + b[3], b[10] + b[2], b[9] + b[1], b[8] + b[0]);

        var v;
        r[0] = (v = 0x800000 + z[0] + (y[8] -x[8] -z[8] + x[0] -0x80) * 38) & 0xFFFF;
        r[1] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[1] + (y[9] -x[9] -z[9] + x[1]) * 38) & 0xFFFF;
        r[2] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[2] + (y[10] -x[10] -z[10] + x[2]) * 38) & 0xFFFF;
        r[3] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[3] + (y[11] -x[11] -z[11] + x[3]) * 38) & 0xFFFF;
        r[4] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[4] + (y[12] -x[12] -z[12] + x[4]) * 38) & 0xFFFF;
        r[5] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[5] + (y[13] -x[13] -z[13] + x[5]) * 38) & 0xFFFF;
        r[6] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[6] + (y[14] -x[14] -z[14] + x[6]) * 38) & 0xFFFF;
        r[7] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[7] + (y[15] -x[15] -z[15] + x[7]) * 38) & 0xFFFF;
        r[8] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[8] + y[0] -x[0] -z[0] + x[8] * 38) & 0xFFFF;
        r[9] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[9] + y[1] -x[1] -z[1] + x[9] * 38) & 0xFFFF;
        r[10] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[10] + y[2] -x[2] -z[2] + x[10] * 38) & 0xFFFF;
        r[11] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[11] + y[3] -x[3] -z[3] + x[11] * 38) & 0xFFFF;
        r[12] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[12] + y[4] -x[4] -z[4] + x[12] * 38) & 0xFFFF;
        r[13] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[13] + y[5] -x[5] -z[5] + x[13] * 38) & 0xFFFF;
        r[14] = (v = 0x7fff80 + ((v / 0x10000) | 0) + z[14] + y[6] -x[6] -z[6] + x[14] * 38) & 0xFFFF;
        var r15 = 0x7fff80 + ((v / 0x10000) | 0) + z[15] + y[7] -x[7] -z[7] + x[15] * 38;
        c255lreduce(r, r15);
    }

    function c255lreduce (a, a15) {
        var v = a15;
        a[15] = v & 0x7FFF;
        v = ((v / 0x8000) | 0) * 19;
        for (var i = 0; i <= 14; ++i) {
            a[i] = (v += a[i]) & 0xFFFF;
            v = ((v / 0x10000) | 0);
        }

        a[15] += v;
    }

    function c255laddmodp (r, a, b) {
        var v;
        r[0] = (v = (((a[15] / 0x8000) | 0) + ((b[15] / 0x8000) | 0)) * 19 + a[0] + b[0]) & 0xFFFF;
        for (var i = 1; i <= 14; ++i)
            r[i] = (v = ((v / 0x10000) | 0) + a[i] + b[i]) & 0xFFFF;

        r[15] = ((v / 0x10000) | 0) + (a[15] & 0x7FFF) + (b[15] & 0x7FFF);
    }

    function c255lsubmodp (r, a, b) {
        var v;
        r[0] = (v = 0x80000 + (((a[15] / 0x8000) | 0) - ((b[15] / 0x8000) | 0) - 1) * 19 + a[0] - b[0]) & 0xFFFF;
        for (var i = 1; i <= 14; ++i)
            r[i] = (v = ((v / 0x10000) | 0) + 0x7fff8 + a[i] - b[i]) & 0xFFFF;

        r[15] = ((v / 0x10000) | 0) + 0x7ff8 + (a[15] & 0x7FFF) - (b[15] & 0x7FFF);
    }

    function c255lmulasmall (r, a, m) {
        var v;
        r[0] = (v = a[0] * m) & 0xFFFF;
        for (var i = 1; i <= 14; ++i)
            r[i] = (v = ((v / 0x10000) | 0) + a[i]*m) & 0xFFFF;

        var r15 = ((v / 0x10000) | 0) + a[15]*m;
        c255lreduce(r, r15);
    }

    //endregion

    /********************* Elliptic curve *********************/

    /* y^2 = x^3 + 486662 x^2 + x  over GF(2^255-19) */

    /* t1 = ax + az
     * t2 = ax - az  */
    function mont_prep (t1, t2, ax, az) {
        add(t1, ax, az);
        sub(t2, ax, az);
    }

    /* A = P + Q   where
     *  X(A) = ax/az
     *  X(P) = (t1+t2)/(t1-t2)
     *  X(Q) = (t3+t4)/(t3-t4)
     *  X(P-Q) = dx
     * clobbers t1 and t2, preserves t3 and t4  */
    function mont_add (t1, t2, t3, t4, ax, az, dx) {
        mul(ax, t2, t3);
        mul(az, t1, t4);
        add(t1, ax, az);
        sub(t2, ax, az);
        sqr(ax, t1);
        sqr(t1, t2);
        mul(az, t1, dx);
    }

    /* B = 2 * Q   where
     *  X(B) = bx/bz
     *  X(Q) = (t3+t4)/(t3-t4)
     * clobbers t1 and t2, preserves t3 and t4  */
    function mont_dbl (t1, t2, t3, t4, bx, bz) {
        sqr(t1, t3);
        sqr(t2, t4);
        mul(bx, t1, t2);
        sub(t2, t1, t2);
        mul_small(bz, t2, 121665);
        add(t1, t1, bz);
        mul(bz, t1, t2);
    }

    /* Y^2 = X^3 + 486662 X^2 + X
     * t is a temporary  */
    function x_to_y2 (t, y2, x) {
        sqr(t, x);
        mul_small(y2, x, 486662);
        add(t, t, y2);
        add(t, t, C1);
        mul(y2, t, x);
    }

    /* P = kG   and  s = sign(P)/k  */
    function core (Px, s, k, Gx) {
        var dx = createUnpackedArray();
        var t1 = createUnpackedArray();
        var t2 = createUnpackedArray();
        var t3 = createUnpackedArray();
        var t4 = createUnpackedArray();
        var x = [createUnpackedArray(), createUnpackedArray()];
        var z = [createUnpackedArray(), createUnpackedArray()];
        var i, j;

        /* unpack the base */
        if (Gx !== null)
            unpack(dx, Gx);
        else
            set(dx, 9);

        /* 0G = point-at-infinity */
        set(x[0], 1);
        set(z[0], 0);

        /* 1G = G */
        cpy(x[1], dx);
        set(z[1], 1);<commit_msg>still porting<commit_after><?php
/* Ported to PHP from Javascript by <NAME> 1/29/15
 * Ported to JavaScript from Java 07/01/14.
 *
 * Ported from C to Java by <NAME> [sahn0], 23/02/08.
 * Original: http://cds.xs4all.nl:8081/ecdh/
 */
/* Generic 64-bit integer implementation of Curve25519 ECDH
 * Written by <NAME>, 200608242056
 * Public domain.
 *
 * Based on work by <NAME>, http://cr.yp.to/ecdh.html
 */

class curve25519 {

    //region Constants

    $KEY_SIZE = 32;

    /* array length */
    $UNPACKED_SIZE = 16;

    /* group order (a prime near 2^252+2^124) */
    $ORDER = [
        237, 211, 245, 92,
        26, 99, 18, 88,
        214, 156, 247, 162,
        222, 249, 222, 20,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 16
    ];

    /* smallest multiple of the order that's >= 2^255 */
   $ORDER_TIMES_8 = [
        104, 159, 174, 231,
        210, 24, 147, 192,
        178, 230, 188, 23,
        245, 206, 247, 166,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 128
    ];

    /* constants 2Gy and 1/(2Gy) */
    $BASE_2Y = [
        22587, 610, 29883, 44076,
        15515, 9479, 25859, 56197,
        23910, 4462, 17831, 16322,
        62102, 36542, 52412, 16035
    ];

    $BASE_R2Y = [
        5744, 16384, 61977, 54121,
        8776, 18501, 26522, 34893,
        23833, 5823, 55924, 58749,
        24147, 14085, 13606, 6080
    ];

    $C1 = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    $C9 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    $C486671 = [0x6D0F, 0x0007, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    $C39420360 = [0x81C8, 0x0259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    $P25 = 33554431; /* (1 << 25) - 1 */
    $P26 = 67108863; /* (1 << 26) - 1 */

    //#endregion

    //region Key Agreement

    /* Private key clamping
     *   k [out] your private key for key agreement
     *   k  [in]  32 random bytes
     */
    function clamp ($k) {
        $k[31] &= 0x7F;
        $k[31] |= 0x40;
        $k[ 0] &= 0xF8;
        return $k;
    }

    //endregion

    //region radix 2^8 math

    function cpy32 ($d, $s) {
        for ($i = 0; $i < 32; $i++)
            $d[$i] = $s[$i];
    }

    /* p[m..n+m-1] = q[m..n+m-1] + z * x */
    /* n is the size of x */
    /* n+m is the size of p and q */
    function mula_small ($p, $q, $m, $x, $n, $z) {
        $m = $m | 0;
        $n = $n | 0;
        $z = $z | 0;

        $v = 0;
        for ($i = 0; $i < $n; ++$i) {
            $v += ($q[$i + $m] & 0xFF) + $z * ($x[$i] & 0xFF);
            $p[$i + $m] = ($v & 0xFF);
            $v >>= 8;
        }

        return $v;
    }

    /* p += x * y * z  where z is a small integer
     * x is size 32, y is size t, p is size 32+t
     * y is allowed to overlap with p+32 if you don't care about the upper half  */
    function mula32 ($p, $x, $y, $t, $z) {
        $t = $t | 0;
        $z = $z | 0;

        $n = 31;
        $w = 0;
        $i = 0;
        for (; $i < $t; $i++) {
            $zy = $z * ($y[$i] & 0xFF);
            $w += mula_small($p, $p, $i, $x, $n, $zy) + ($p[$i+$n] & 0xFF) + $zy * ($x[$n] & 0xFF);
            $p[$i + $n] = $w & 0xFF;
            $w >>= 8;
        }
        $p[$i + $n] = ($w + ($p[$i + $n] & 0xFF)) & 0xFF;
        return $w >> 8;
    }

    /* divide r (size n) by d (size t), returning quotient q and remainder r
     * quotient is size n-t+1, remainder is size t
     * requires t > 0 && d[t-1] !== 0
     * requires that r[-1] and d[-1] are valid memory locations
     * q may overlap with r+t */
    function divmod ($q, $r, $n, $d, $t) {
        $n = $n | 0;
        $t = $t | 0;

        $rn = 0;
        $dt = ($d[$t - 1] & 0xFF) << 8;
        if ($t > 1)
            $dt |= ($d[$t - 2] & 0xFF);

        while ($n-- >= $t) {
            $z = ($rn << 16) | (($r[$n] & 0xFF) << 8);
            if ($n > 0)
                $z |= ($r[$n - 1] & 0xFF);

            $i = $n - $t + 1;
            $z /= $dt;
            $rn += mula_small($r, $r, $i, $d, $t, -$z);
            $q[$i] = ($z + $rn) & 0xFF;
            /* rn is 0 or -1 (underflow) */
            mula_small($r, $r, $i, $d, $t, -$rn);
            $rn = $r[$n] & 0xFF;
            $r[$n] = 0;
        }

        $r[$t-1] = $rn & 0xFF;
    }

    function numsize ($x, $n) {
        while ($n-- !== 0 && $x[$n] === 0) { }
        return $n + 1;
    }

    /* Returns x if a contains the gcd, y if b.
     * Also, the returned buffer contains the inverse of a mod b,
     * as 32-byte signed.
     * x and y must have 64 bytes space for temporary use.
     * requires that a[-1] and b[-1] are valid memory locations  */
    function egcd32 ($x, $y, $a, $b) {
        $an, $bn = 32, $qn, $i;
        for ($i = 0; $i < 32; $i++)
            $x[$i] = $y[$i] = 0;
        $x[0] = 1;
        $an = numsize($a, 32);
        if ($an === 0)
            return $y; /* division by zero */
        $temp = array();
        while (true) {
            $qn = $bn - $an + 1;
            divmod($temp, $b, $bn, $a, $an);
            $bn = numsize($b, $bn);
            if ($bn === 0)
                return $x;
            mula32($y, $x, $temp, $qn, -1);

            $qn = $an - $bn + 1;
            divmod($temp, $a, $an, $b, $bn);
            $an = numsize($a, $an);
            if ($an === 0)
                return $y;
            mula32($x, $y, $temp, $qn, -1);
        }
    }

    //endregion

    //region radix 2^25.5 GF(2^255-19) math

    //region pack / unpack

    /* Convert to internal format from little-endian byte format */
    function unpack ($x, $m) {
        for ($i = 0; $i < $KEY_SIZE; $i += 2)
            $x[$i / 2] = $m[$i] & 0xFF | (($m[$i + 1] & 0xFF) << 8);
    }

    /* Check if reduced-form input >= 2^255-19 */
    function is_overflow ($x) {
        return (
            (($x[0] > $P26 - 19)) &&
                (($x[1] & $x[3] & $x[5] & $x[7] & $x[9]) === $P25) &&
                (($x[2] & $x[4] & $x[6] & $x[8]) === $P26)
            ) || ($x[9] > $P25);
    }

    /* Convert from internal format to little-endian byte format.  The
     * number must be in a reduced form which is output by the following ops:
     *     unpack, mul, sqr
     *     set --  if input in range 0 .. P25
     * If you're unsure if the number is reduced, first multiply it by 1.  */
    function pack ($x, $m) {
        for ($i = 0; $i < $UNPACKED_SIZE; ++$i) {
            $m[2 * $i] = $x[$i] & 0x00FF;
            $m[2 * $i + 1] = ($x[$i] & 0xFF00) >> 8;
        }
    }

    //endregion

    function createUnpackedArray () {
        return array();
    }

    /* Copy a number */
    function cpy ($d, $s) {
        for ($i = 0; $i < $UNPACKED_SIZE; ++$i)
            $d[$i] = $s[$i];
    }

    /* Set a number to value, which must be in range -185861411 .. 185861411 */
    function set ($d, $s) {
        $d[0] = $s;
        for (var $i = 1; $i < $UNPACKED_SIZE; ++$i)
            $d[$i] = 0;
    }


    // shortcuts from standard curve25519
    private $zero = [0,0,0,0, 0,0,0,0, 0,0];
    private $one  = [1,0,0,0, 0,0,0,0, 0,0];
    private $nine = [9,0,0,0, 0,0,0,0, 0,0];
    function add($a, $b) {
        return [
            $a[0] + $b[0], $a[1] + $b[1], $a[2] + $b[2], $a[3] + $b[3], $a[4] + $b[4],
            $a[5] + $b[5], $a[6] + $b[6], $a[7] + $b[7], $a[8] + $b[8], $a[9] + $b[9]
        ];
    }
    function sub($a, $b) {
        $r = [
            ($c = 0x7ffffda + $a[0] - $b[0]             ) & 0x3ffffff,
            ($c = 0x3fffffe + $a[1] - $b[1] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[2] - $b[2] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[3] - $b[3] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[4] - $b[4] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[5] - $b[5] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[6] - $b[6] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[7] - $b[7] + ($c >> 26)) & 0x1ffffff,
            ($c = 0x7fffffe + $a[8] - $b[8] + ($c >> 25)) & 0x3ffffff,
            ($c = 0x3fffffe + $a[9] - $b[9] + ($c >> 26)) & 0x1ffffff,
        ];
        $r[0] += 19 * ($c >> 25);
        return $r;
    }
    function mul($a, $b) {
        list($s0, $s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) = $a;
        list($r0, $r1, $r2, $r3, $r4, $r5, $r6, $r7, $r8, $r9) = $b;
        $r1_2 = $r1 * 2;
        $r3_2 = $r3 * 2;
        $r5_2 = $r5 * 2;
        $r2_19 = $r2 * 19;
        $r4_19 = $r4 * 19;
        $r5_19 = $r5 * 19;
        $r6_19 = $r6 * 19;
        $r7_19 = $r7 * 19;
        $r8_19 = $r8 * 19;
        $r9_19 = $r9 * 19;
        $r3_38 = $r3 * 38;
        $r5_38 = $r5 * 38;
        $r7_38 = $r7 * 38;
        $r9_38 = $r9 * 38;
        $r = [
            ($c = $m0 = ($r0 * $s0) + ($r1 * 38 * $s9) + ($r2_19 * $s8) + ($r3_38   * $s7) + ($r4_19 * $s6) + ($r5_38 * $s5) + ($r6_19 * $s4) + ($r7_38  * $s3) + ($r8_19 * $s2) + ($r9_38 * $s1)             ) & 0x3ffffff,
            ($c = $m1 = ($r0 * $s1) + ($r1      * $s0) + ($r2_19 * $s9) + ($r3 * 19 * $s8) + ($r4_19 * $s7) + ($r5_19 * $s6) + ($r6_19 * $s5) + ($r7_19  * $s4) + ($r8_19 * $s3) + ($r9_19 * $s2) + ($c >> 26)) & 0x1ffffff,
            ($c = $m2 = ($r0 * $s2) + ($r1_2    * $s1) + ($r2    * $s0) + ($r3_38   * $s9) + ($r4_19 * $s8) + ($r5_38 * $s7) + ($r6_19 * $s6) + ($r7_38  * $s5) + ($r8_19 * $s4) + ($r9_38 * $s3) + ($c >> 25)) & 0x3ffffff,
            ($c = $m3 = ($r0 * $s3) + ($r1      * $s2) + ($r2    * $s1) + ($r3      * $s0) + ($r4_19 * $s9) + ($r5_19 * $s8) + ($r6_19 * $s7) + ($r7_19  * $s6) + ($r8_19 * $s5) + ($r9_19 * $s4) + ($c >> 26)) & 0x1ffffff,
            ($c = $m4 = ($r0 * $s4) + ($r1_2    * $s3) + ($r2    * $s2) + ($r3_2    * $s1) + ($r4    * $s0) + ($r5_38 * $s9) + ($r6_19 * $s8) + ($r7_38  * $s7) + ($r8_19 * $s6) + ($r9_38 * $s5) + ($c >> 25)) & 0x3ffffff,
            ($c = $m5 = ($r0 * $s5) + ($r1      * $s4) + ($r2    * $s3) + ($r3      * $s2) + ($r4    * $s1) + ($r5    * $s0) + ($r6_19 * $s9) + ($r7_19  * $s8) + ($r8_19 * $s7) + ($r9_19 * $s6) + ($c >> 26)) & 0x1ffffff,
            ($c = $m6 = ($r0 * $s6) + ($r1_2    * $s5) + ($r2    * $s4) + ($r3_2    * $s3) + ($r4    * $s2) + ($r5_2  * $s1) + ($r6    * $s0) + ($r7_38  * $s9) + ($r8_19 * $s8) + ($r9_38 * $s7) + ($c >> 25)) & 0x3ffffff,
            ($c = $m7 = ($r0 * $s7) + ($r1      * $s6) + ($r2    * $s5) + ($r3      * $s4) + ($r4    * $s3) + ($r5    * $s2) + ($r6    * $s1) + ($r7     * $s0) + ($r8_19 * $s9) + ($r9_19 * $s8) + ($c >> 26)) & 0x1ffffff,
            ($c = $m8 = ($r0 * $s8) + ($r1_2    * $s7) + ($r2    * $s6) + ($r3_2    * $s5) + ($r4    * $s4) + ($r5_2  * $s3) + ($r6    * $s2) + ($r7 * 2 * $s1) + ($r8    * $s0) + ($r9_38 * $s9) + ($c >> 25)) & 0x3ffffff,
            ($c = $m9 = ($r0 * $s9) + ($r1      * $s8) + ($r2    * $s7) + ($r3      * $s6) + ($r4    * $s5) + ($r5    * $s4) + ($r6    * $s3) + ($r7     * $s2) + ($r8    * $s1) + ($r9    * $s0) + ($c >> 26)) & 0x1ffffff
        ];
        $r[0] += ($c = 19 * ($c >> 25)) & 0x3ffffff;
        $r[1] +=            ($c >> 26);
        return $r;
    }
    function sqr($a, $n = 1) {
        list($r0, $r1, $r2, $r3, $r4, $r5, $r6, $r7, $r8, $r9) = $a;
        do {
            $r0_2 = $r0 * 2;
            $r1_2 = $r1 * 2;
            $r2_2 = $r2 * 2;
            $r3_2 = $r3 * 2;
            $r4_2 = $r4 * 2;
            $r5_2 = $r5 * 2;
            $r7_2 = $r7 * 2;
            $r6_19 = $r6 * 19;
            $r7_38 = $r7 * 38;
            $r8_19 = $r8 * 19;
            $r9_38 = $r9 * 38;
            $s0 = ($r0   * $r0) + ($r5 * $r5 * 38) + ($r6_19 * $r4_2) + ($r7_38 * $r3_2) + ($r8_19 * $r2_2) + ($r9_38 * $r1_2);
            $s1 = ($r0_2 * $r1)                    + ($r6_19 * $r5_2) + ($r7_38 * $r4  ) + ($r8_19 * $r3_2) + ($r9_38 * $r2  );
            $s2 = ($r0_2 * $r2) + ($r1_2 * $r1)    + ($r6_19 * $r6  ) + ($r7_38 * $r5_2) + ($r8_19 * $r4_2) + ($r9_38 * $r3_2);
            $s3 = ($r0_2 * $r3) + ($r1_2 * $r2)                       + ($r7_38 * $r6  ) + ($r8_19 * $r5_2) + ($r9_38 * $r4  );
            $s4 = ($r0_2 * $r4) + ($r1_2 * $r3_2) + ($r2   * $r2)     + ($r7_38 * $r7  ) + ($r8_19 * $r6*2) + ($r9_38 * $r5_2);
            $s5 = ($r0_2 * $r5) + ($r1_2 * $r4  ) + ($r2_2 * $r3)                        + ($r8_19 * $r7_2) + ($r9_38 * $r6  );
            $s6 = ($r0_2 * $r6) + ($r1_2 * $r5_2) + ($r2_2 * $r4) + ($r3_2 * $r3)        + ($r8_19 * $r8  ) + ($r9_38 * $r7_2);
            $s7 = ($r0_2 * $r7) + ($r1_2 * $r6  ) + ($r2_2 * $r5) + ($r3_2 * $r4  )                         + ($r9_38 * $r8  );
            $s8 = ($r0_2 * $r8) + ($r1_2 * $r7_2) + ($r2_2 * $r6) + ($r3_2 * $r5_2) + ($r4 * $r4  )         + ($r9_38 * $r9  );
            $s9 = ($r0_2 * $r9) + ($r1_2 * $r8  ) + ($r2_2 * $r7) + ($r3_2 * $r6  ) + ($r4 * $r5_2);
            $r0 = ($c = $s0             ) & 0x3ffffff;
            $r1 = ($c = $s1 + ($c >> 26)) & 0x1ffffff;
            $r2 = ($c = $s2 + ($c >> 25)) & 0x3ffffff;
            $r3 = ($c = $s3 + ($c >> 26)) & 0x1ffffff;
            $r4 = ($c = $s4 + ($c >> 25)) & 0x3ffffff;
            $r5 = ($c = $s5 + ($c >> 26)) & 0x1ffffff;
            $r6 = ($c = $s6 + ($c >> 25)) & 0x3ffffff;
            $r7 = ($c = $s7 + ($c >> 26)) & 0x1ffffff;
            $r8 = ($c = $s8 + ($c >> 25)) & 0x3ffffff;
            $r9 = ($c = $s9 + ($c >> 26)) & 0x1ffffff;
            $r0 += ($c = 19 * ($c >> 25)) & 0x3ffffff;
            $r1 +=            ($c >> 26);
        } while (--$n);
        return [$r0, $r1, $r2, $r3, $r4, $r5, $r6, $r7, $r8, $r9];
    }
    function mul121665($in) {
        $r = [
            ($c = $in[0] * 121665             ) & 0x3ffffff,
            ($c = $in[1] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[2] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[3] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[4] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[5] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[6] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[7] * 121665 + ($c >> 26)) & 0x1ffffff,
            ($c = $in[8] * 121665 + ($c >> 25)) & 0x3ffffff,
            ($c = $in[9] * 121665 + ($c >> 26)) & 0x1ffffff,
        ];
        $r[0] += 19 * ($c >> 25);
        return $r;
    }
    function scalarmult($f, $c) {
        $t = $this->one;
        $u = $this->zero;
        $v = $this->one;
        $w = $c;
        $swapBit = 1;
        $i = 254;
        while ($i --> 2) {
            $x = $this->add($w, $v);
            $v = $this->sub($w, $v);
            $y = $this->add($t, $u);
            $u = $this->sub($t, $u);
            $t = $this->mul($y, $v);
            $u = $this->mul($x, $u);
            $z = $this->add($t, $u);
            $u = $this->sqr($this->sub($t, $u));
            $t = $this->sqr($z);
            $u = $this->mul($u, $c);
            $x = $this->sqr($x);
            $v = $this->sqr($v);
            $w = $this->mul($x, $v);
            $v = $this->sub($x, $v);
            $v = $this->mul($v, $this->add($this->mul121665($v), $x));
            $b = ($f[$i >> 3] >> ($i & 7)) & 1;
            $swap = $b ^ $swapBit;
            list($w, $t) = [[$w, $t], [$t, $w]][$swap];
            list($v, $u) = [[$v, $u], [$u, $v]][$swap];
            $swapBit = $b;
        }
        $i = 3;
        while ($i--) {
            $x = $this->sqr($this->add($w, $v));
            $v = $this->sqr($this->sub($w, $v));
            $w = $this->mul($x, $v);
            $v = $this->sub($x, $v);
            $v = $this->mul($v, $this->add($this->mul121665($v), $x));
        }
        $a = $this->sqr($v);
        $b = $this->mul($this->sqr($a, 2), $v);
        $a = $this->mul($b, $a);
        $b = $this->mul($this->sqr($a), $b);
        $b = $this->mul($this->sqr($b, 5), $b);
        $c = $this->mul($this->sqr($b, 10), $b);
        $b = $this->mul($this->sqr($this->mul($this->sqr($c, 20), $c), 10), $b);
        $c = $this->mul($this->sqr($b, 50), $b);
        $r = $this->mul($w, $this->mul($this->sqr($this->mul($this->sqr($this->mul($this->sqr($c, 100), $c), 50), $b), 5), $a));
        $r = [
            ($c = $r[0] + 0x4000000             ) & 0x3ffffff,
            ($c = $r[1] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[2] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[3] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[4] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[5] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[6] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[7] + 0x1ffffff + ($c >> 26)) & 0x1ffffff,
            ($c = $r[8] + 0x3ffffff + ($c >> 25)) & 0x3ffffff,
            ($c = $r[9] + 0x1ffffff + ($c >> 26)) & 0x1ffffff
        ];
        return pack('V8',
             $r[0]        | ($r[1] << 26),
            ($r[1] >>  6) | ($r[2] << 19),
            ($r[2] >> 13) | ($r[3] << 13),
            ($r[3] >> 19) | ($r[4] <<  6),
             $r[5]        | ($r[6] << 25),
            ($r[6] >>  7) | ($r[7] << 19),
            ($r[7] >> 13) | ($r[8] << 12),
            ($r[8] >> 20) | ($r[9] <<  6)
        );
    }
    function clamp($secret)
    {
        $e = array_values(unpack('C32', $secret));
        $e[0]  &= 0xf8;
        $e[31] &= 0x7f;
        $e[31] |= 0x40;
        return $e;
    }
    function getPublic($secret)
    {
        if (!is_string($secret) || strlen($secret) !== 32) {
            throw new InvalidArgumentException('Secret must be a 32 byte string');
        }
        return $this->scalarmult($this->clamp($secret), $this->nine);
    }
    function getShared($secret, $public)
    {
        if (!is_string($secret) || strlen($secret) !== 32) {
            throw new InvalidArgumentException('Secret must be a 32 byte string');
        }
        if (!is_string($public) || strlen($public) !== 32) {
            throw new InvalidArgumentException('Public must be a 32 byte string');
        }
        $w = unpack('V8', $public);
        $r = [
              $w[1]                         & 0x3ffffff, // 26
            (($w[1] >> 26) | ($w[2] <<  6)) & 0x1ffffff, // 25 - 51
            (($w[2] >> 19) | ($w[3] << 13)) & 0x3ffffff, // 26 - 77
            (($w[3] >> 13) | ($w[4] << 19)) & 0x1ffffff, // 25 - 102
             ($w[4] >>  6)                  & 0x3ffffff, // 26 - 128
              $w[5]                         & 0x1ffffff, // 25 - 153
            (($w[5] >> 25) | ($w[6] <<  7)) & 0x3ffffff, // 26 - 179
            (($w[6] >> 19) | ($w[7] << 13)) & 0x1ffffff, // 25 - 204
            (($w[7] >> 12) | ($w[8] << 20)) & 0x3ffffff, // 26 - 230
             ($w[8] >> 6)                   & 0x1ffffff, // 25 - 255
        ];
        return $this->scalarmult($this->clamp($secret), $r);
    }


    /* Add/subtract two numbers.  The inputs must be in reduced form, and the
     * output isn't, so to do another addition or subtraction on the output,
     * first multiply it by one to reduce it. */
    //$add = $c255laddmodp;
    //$sub = $c255lsubmodp;

    /* Multiply a number by a small integer in range -185861411 .. 185861411.
     * The output is in reduced form, the input x need not be.  x and xy may point
     * to the same buffer. */
    $mul_small = $c255lmulasmall;

    /* Multiply two numbers.  The output is in reduced form, the inputs need not be. */
    $mul = $c255lmulmodp;

    /* Square a number.  Optimization of  mul25519(x2, x, x)  */
    $sqr = $c255lsqrmodp;

    /* Calculates a reciprocal.  The output is in reduced form, the inputs need not
     * be.  Simply calculates  y = x^(p-2)  so it's not too fast. */
    /* When sqrtassist is true, it instead calculates y = x^((p-5)/8) */
    function recip ($y, $x, $sqrtassist) {
        $t0 = createUnpackedArray();
        $t1 = createUnpackedArray();
        $t2 = createUnpackedArray();
        $t3 = createUnpackedArray();
        $t4 = createUnpackedArray();

        /* the chain for x^(2^255-21) is straight from djb's implementation */
        $i;
        sqr($t1, $x); /*  2 === 2 * 1 */
        sqr($t2, $t1); /*  4 === 2 * 2    */
        sqr($t0, $t2); /*  8 === 2 * 4    */
        mul($t2, $t0, $x); /*  9 === 8 + 1 */
        mul($t0, $t2, $t1); /* 11 === 9 + 2    */
        sqr($t1, $t0); /* 22 === 2 * 11   */
        mul($t3, $t1, $t2); /* 31 === 22 + 9 === 2^5   - 2^0   */
        sqr($t1, $t3); /* 2^6   - 2^1 */
        sqr($t2, $t1); /* 2^7   - 2^2 */
        sqr($t1, $t2); /* 2^8   - 2^3 */
        sqr($t2, $t1); /* 2^9   - 2^4 */
        sqr($t1, $t2); /* 2^10  - 2^5 */
        mul($t2, $t1, $t3); /* 2^10  - 2^0 */
        sqr($t1, $t2); /* 2^11  - 2^1 */
        sqr($t3, $t1); /* 2^12  - 2^2 */
        for ($i = 1; $i < 5; $i++) {
            sqr($t1, $t3);
            sqr($t3, $t1);
        } /* t3 */ /* 2^20  - 2^10  */
        mul($t1, $t3, $t2); /* 2^20  - 2^0 */
        sqr($t3, $t1); /* 2^21  - 2^1 */
        sqr($t4, $t3); /* 2^22  - 2^2 */
        for ($i = 1; $i < 10; $i++) {
            sqr($t3, $t4);
            sqr($t4, $t3);
        } /* t4 */ /* 2^40  - 2^20  */
        mul($t3, $t4, $t1); /* 2^40  - 2^0 */
        for ($i = 0; $i < 5; $i++) {
            sqr($t1, $t3);
            sqr($t3, $t1);
        } /* t3 */ /* 2^50  - 2^10  */
        mul($t1, $t3, $t2); /* 2^50  - 2^0 */
        sqr($t2, $t1); /* 2^51  - 2^1 */
        sqr($t3, $t2); /* 2^52  - 2^2 */
        for ($i = 1; $i < 25; $i++) {
            sqr($t2, $t3);
            sqr($t3, $t2);
        } /* t3 */ /* 2^100 - 2^50 */
        mul($t2, $t3, $t1); /* 2^100 - 2^0 */
        sqr($t3, $t2); /* 2^101 - 2^1 */
        sqr($t4, $t3); /* 2^102 - 2^2 */
        for ($i = 1; $i < 50; $i++) {
            sqr($t3, $t4);
            sqr($t4, $t3);
        } /* t4 */ /* 2^200 - 2^100 */
        mul($t3, $t4, $t2); /* 2^200 - 2^0 */
        for ($i = 0; $i < 25; $i++) {
            sqr($t4, $t3);
            sqr($t3, $t4);
        } /* t3 */ /* 2^250 - 2^50  */
        mul($t2, $t3, $t1); /* 2^250 - 2^0 */
        sqr($t1, $t2); /* 2^251 - 2^1 */
        sqr($t2, $t1); /* 2^252 - 2^2 */
        if ($sqrtassist !== 0) {
            mul($y, $x, $t2); /* 2^252 - 3 */
        } else {
            sqr($t1, $t2); /* 2^253 - 2^3 */
            sqr($t2, $t1); /* 2^254 - 2^4 */
            sqr($t1, $t2); /* 2^255 - 2^5 */
            mul($y, $t1, $t0); /* 2^255 - 21   */
        }
    }

    /* checks if x is "negative", requires reduced input */
    function is_negative ($x) {
        $isOverflowOrNegative = is_overflow($x) || $x[9] < 0;
        $leastSignificantBit = $x[0] & 1;
        return (($isOverflowOrNegative ? 1 : 0) ^ $leastSignificantBit) & 0xFFFFFFFF;
    }

    /* a square root */
    function sqrt ($x, $u) {
        $v = createUnpackedArray();
        $t1 = createUnpackedArray();
        $t2 = createUnpackedArray();

        add($t1, $u, $u); /* t1 = 2u       */
        recip($v, $t1, 1); /* v = (2u)^((p-5)/8)  */
        sqr($x, $v); /* x = v^2       */
        mul($t2, $t1, $x); /* t2 = 2uv^2       */
        sub($t2, $t2, $C1); /* t2 = 2uv^2-1        */
        mul($t1, $v, $t2); /* t1 = v(2uv^2-1)  */
        mul($x, $u, $t1); /* x = uv(2uv^2-1)   */
    }

    //endregion

    //region JavaScript Fast Math

    function c255lsqr8h ($a7, $a6, $a5, $a4, $a3, $a2, $a1, $a0) {
        $r = array();
        $v;
        $r[0] = ($v = $a0*$a0) & 0xFFFF;
        $r[1] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a1) & 0xFFFF;
        $r[2] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a2 + $a1*$a1) & 0xFFFF;
        $r[3] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a3 + 2*$a1*$a2) & 0xFFFF;
        $r[4] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a4 + 2*$a1*$a3 + $a2*$a2) & 0xFFFF;
        $r[5] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a5 + 2*$a1*$a4 + 2*$a2*$a3) & 0xFFFF;
        $r[6] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a6 + 2*$a1*$a5 + 2*$a2*$a4 + $a3*$a3) & 0xFFFF;
       $r[7] = ($v = (($v / 0x10000) | 0) + 2*$a0*$a7 + 2*$a1*$a6 + 2*$a2*$a5 + 2*$a3*$a4) & 0xFFFF;
        $r[8] = ($v = (($v / 0x10000) | 0) + 2*$a1*$a7 + 2*$a2*$a6 + 2*$a3*$a5 + $a4*$a4) & 0xFFFF;
        $r[9] = ($v = (($v / 0x10000) | 0) + 2*$a2*a7 + 2*$a3*$a6 + 2*$a4*$a5) & 0xFFFF;
        $r[10] = ($v = (($v / 0x10000) | 0) + 2*$a3*a7 + 2*$a4*$a6 + $a5*$a5) & 0xFFFF;
        $r[11] = ($v = (($v / 0x10000) | 0) + 2*$a4*a7 + 2*$a5*$a6) & 0xFFFF;
        $r[12] = ($v = (($v / 0x10000) | 0) + 2*$a5*a7 + $a6*$a6) & 0xFFFF;
        $r[13] = ($v = (($v / 0x10000) | 0) + 2*$a6*a7) & 0xFFFF;
        $r[14] = ($v = (($v / 0x10000) | 0) + $a7*$a7) & 0xFFFF;
        $r[15] = (($v / 0x10000) | 0);
        return $r;
    }

    function c255lsqrmodp ($r, $a) {
        $x = c255lsqr8h($a[15], $a[14], $a[13], $a[12], $a[11], $a[10], $a[9], $a[8]);
        $z = c255lsqr8h($a[7], $a[6], $a[5], $a[4], $a[3], $a[2], $a[1], $a[0]);
        $y = c255lsqr8h($a[15] + $a[7], $a[14] + $a[6], $a[13] + $a[5], $a[12] + $a[4], $a[11] + $a[3], $a[10] + $a[2], $a[9] + $a[1], $a[8] + $a[0]);

        $v;
        $r[0] = ($v = 0x800000 + $z[0] + ($y[8] -$x[8] -$z[8] + $x[0] -0x80) * 38) & 0xFFFF;
        $r[1] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[1] + ($y[9] -$x[9] -$z[9] + $x[1]) * 38) & 0xFFFF;
        $r[2] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[2] + ($y[10] -$x[10] -$z[10] + $x[2]) * 38) & 0xFFFF;
        $r[3] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[3] + ($y[11] -$x[11] -$z[11] + $x[3]) * 38) & 0xFFFF;
        $r[4] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[4] + ($y[12] -$x[12] -$z[12] + $x[4]) * 38) & 0xFFFF;
        $r[5] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[5] + ($y[13] -$x[13] -$z[13] + $x[5]) * 38) & 0xFFFF;
        $r[6] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[6] + ($y[14] -$x[14] -$z[14] + $x[6]) * 38) & 0xFFFF;
        $r[7] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[7] + ($y[15] -$x[15] -$z[15] + $x[7]) * 38) & 0xFFFF;
        $r[8] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[8] + $y[0] -$x[0] -$z[0] + $x[8] * 38) & 0xFFFF;
        $r[9] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[9] + $y[1] -$x[1] -$z[1] + $x[9] * 38) & 0xFFFF;
        $r[10] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[10] + $y[2] -$x[2] -$z[2] + $x[10] * 38) & 0xFFFF;
        $r[11] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[11] + $y[3] -$x[3] -$z[3] + $x[11] * 38) & 0xFFFF;
        $r[12] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[12] + $y[4] -$x[4] -$z[4] + $x[12] * 38) & 0xFFFF;
        $r[13] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[13] + $y[5] -$x[5] -$z[5] + $x[13] * 38) & 0xFFFF;
        $r[14] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[14] + $y[6] -$x[6] -$z[6] + $x[14] * 38) & 0xFFFF;
        $r15 = 0x7fff80 + (($v / 0x10000) | 0) + $z[15] + $y[7] -$x[7] -$z[7] + $x[15] * 38;
        c255lreduce($r, $r15);
    }

    function c255lmul8h ($a7, $a6, $a5, $a4, $a3, $a2, $a1, $a0, $b7, $b6, $b5, $b4, $b3, $b2, $b1, $b0) {
        $r = [];
        $v;
        $r[0] = ($v = $a0*$b0) & 0xFFFF;
        $r[1] = ($v = (($v / 0x10000) | 0) + $a0*$b1 + $a1*$b0) & 0xFFFF;
        $r[2] = ($v = (($v / 0x10000) | 0) + $a0*$b2 + $a1*$b1 + $a2*$b0) & 0xFFFF;
        $r[3] = ($v = (($v / 0x10000) | 0) + $a0*$b3 + $a1*$b2 + $a2*$b1 + $a3*$b0) & 0xFFFF;
        $r[4] = ($v = (($v / 0x10000) | 0) + $a0*$b4 + $a1*$b3 + $a2*$b2 + $a3*$b1 + $a4*$b0) & 0xFFFF;
        $r[5] = ($v = (($v / 0x10000) | 0) + $a0*$b5 + $a1*$b4 + $a2*$b3 + $a3*$b2 + $a4*$b1 + $a5*$b0) & 0xFFFF;
        $r[6] = ($v = (($v / 0x10000) | 0) + $a0*$b6 + $a1*$b5 + $a2*$b4 + $a3*$b3 + $a4*$b2 + $a5*$b1 + $a6*$b0) & 0xFFFF;
        $r[7] = ($v = (($v / 0x10000) | 0) + $a0*$b7 + $a1*$b6 + $a2*$b5 + $a3*$b4 + $a4*$b3 + $a5*$b2 + $a6*$b1 + $a7*$b0) & 0xFFFF;
        $r[8] = ($v = (($v / 0x10000) | 0) + $a1*$b7 + $a2*$b6 + $a3*$b5 + $a4*$b4 + $a5*$b3 + $a6*$b2 + $a7*$b1) & 0xFFFF;
        $r[9] = ($v = (($v / 0x10000) | 0) + $a2*$b7 + $a3*$b6 + $a4*$b5 + $a5*$b4 + $a6*$b3 + $a7*$b2) & 0xFFFF;
        $r[10] = ($v = (($v / 0x10000) | 0) + $a3*$b7 + $a4*$b6 + $a5*$b5 + $a6*$b4 + $a7*$b3) & 0xFFFF;
        $r[11] = ($v = (($v / 0x10000) | 0) + $a4*$b7 + $a5*$b6 + $a6*$b5 + $a7*$b4) & 0xFFFF;
        $r[12] = ($v = (($v / 0x10000) | 0) + $a5*$b7 + $a6*$b6 + $a7*$b5) & 0xFFFF;
        $r[13] = ($v = (($v / 0x10000) | 0) + $a6*$b7 + $a7*$b6) & 0xFFFF;
        $r[14] = ($v = (($v / 0x10000) | 0) + $a7*$b7) & 0xFFFF;
        $r[15] = (($v / 0x10000) | 0);
        return r;
    }

    function c255lmulmodp ($r, $a, $b) {
        // Karatsuba multiplication scheme: x*y = (b^2+b)*x1*y1 - b*(x1-x0)*(y1-y0) + (b+1)*x0*y0
        $x = c255lmul8h($a[15], $a[14], $a[13], $a[12], $a[11], $a[10], $a[9], $a[8], $b[15], $b[14], $b[13], $b[12], $b[11], $b[10], $b[9], $b[8]);
        $z = c255lmul8h($a[7], $a[6], $a[5], $a[4], $a[3], $a[2], $a[1], $a[0], $b[7], $b[6], $b[5], $b[4], $b[3], $b[2], $b[1], $b[0]);
        $y = c255lmul8h($a[15] + $a[7], $a[14] + $a[6], $a[13] + $a[5], $a[12] + $a[4], $a[11] + $a[3], $a[10] + $a[2], $a[9] + $a[1], $a[8] + $a[0],
            $b[15] + $b[7], $b[14] + $b[6], $b[13] + $b[5], $b[12] + $b[4], $b[11] + $b[3], $b[10] + $b[2], $b[9] + $b[1], $b[8] + $b[0]);

        $v;
        $r[0] = ($v = 0x800000 + $z[0] + ($y[8] -$x[8] -$z[8] + $x[0] -0x80) * 38) & 0xFFFF;
        $r[1] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[1] + ($y[9] -$x[9] -$z[9] + $x[1]) * 38) & 0xFFFF;
        $r[2] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[2] + ($y[10] -$x[10] -$z[10] + $x[2]) * 38) & 0xFFFF;
        $r[3] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[3] + ($y[11] -$x[11] -$z[11] + $x[3]) * 38) & 0xFFFF;
        $r[4] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[4] + ($y[12] -$x[12] -$z[12] + $x[4]) * 38) & 0xFFFF;
        $r[5] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[5] + ($y[13] -$x[13] -$z[13] + $x[5]) * 38) & 0xFFFF;
        $r[6] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[6] + ($y[14] -$x[14] -$z[14] + $x[6]) * 38) & 0xFFFF;
        $r[7] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[7] + ($y[15] -$x[15] -$z[15] + $x[7]) * 38) & 0xFFFF;
        $r[8] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[8] + $y[0] -$x[0] -$z[0] + $x[8] * 38) & 0xFFFF;
        $r[9] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[9] + $y[1] -$x[1] -$z[1] + $x[9] * 38) & 0xFFFF;
        $r[10] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[10] + $y[2] -$x[2] -$z[2] + $x[10] * 38) & 0xFFFF;
        $r[11] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[11] + $y[3] -$x[3] -$z[3] + $x[11] * 38) & 0xFFFF;
        $r[12] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[12] + $y[4] -$x[4] -$z[4] + $x[12] * 38) & 0xFFFF;
        $r[13] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[13] + $y[5] -$x[5] -$z[5] + $x[13] * 38) & 0xFFFF;
        $r[14] = ($v = 0x7fff80 + (($v / 0x10000) | 0) + $z[14] + $y[6] -$x[6] -$z[6] + $x[14] * 38) & 0xFFFF;
        $r15 = 0x7fff80 + (($v / 0x10000) | 0) + $z[15] + $y[7] -$x[7] -$z[7] + $x[15] * 38;
        c255lreduce($r, $r15);
    }

    function c255lreduce ($a, $a15) {
        $v = $a15;
        $a[15] = $v & 0x7FFF;
        $v = (($v / 0x8000) | 0) * 19;
        for ($i = 0; $i <= 14; ++$i) {
            $a[$i] = ($v += $a[$i]) & 0xFFFF;
            $v = (($v / 0x10000) | 0);
        }

        $a[15] += $v;
    }

    function c255laddmodp ($r, $a, $b) {
        $v;
        $r[0] = ($v = ((($a[15] / 0x8000) | 0) + (($b[15] / 0x8000) | 0)) * 19 + $a[0] + $b[0]) & 0xFFFF;
        for ($i = 1; $i <= 14; ++$i)
            $r[$i] = ($v = (($v / 0x10000) | 0) + $a[$i] + $b[$i]) & 0xFFFF;

        $r[15] = (($v / 0x10000) | 0) + ($a[15] & 0x7FFF) + ($b[15] & 0x7FFF);
    }

    function c255lsubmodp ($r, $a, $b) {
        $v;
        $r[0] = ($v = 0x80000 + ((($a[15] / 0x8000) | 0) - (($b[15] / 0x8000) | 0) - 1) * 19 + $a[0] - $b[0]) & 0xFFFF;
        for ($i = 1; $i <= 14; ++$i)
            $r[$i] = ($v = (($v / 0x10000) | 0) + 0x7fff8 + $a[$i] - $b[$i]) & 0xFFFF;

        $r[15] = (($v / 0x10000) | 0) + 0x7ff8 + ($a[15] & 0x7FFF) - ($b[15] & 0x7FFF);
    }

    function c255lmulasmall ($r, $a, $m) {
        $v;
        $r[0] = ($v = $a[0] * $m) & 0xFFFF;
        for ($i = 1; $i <= 14; ++$i)
            $r[i] = ($v = (($v / 0x10000) | 0) + $a[$i]*$m) & 0xFFFF;

        $r15 = (($v / 0x10000) | 0) + $a[15]*$m;
        c255lreduce($r, $r15);
    }

    //endregion

    /********************* Elliptic curve *********************/

    /* y^2 = x^3 + 486662 x^2 + x  over GF(2^255-19) */

    /* t1 = ax + az
     * t2 = ax - az  */
    function mont_prep ($t1, $t2, $ax, $az) {
        add($t1, $ax, $az);
        sub($t2, $ax, $az);
    }

    /* A = P + Q   where
     *  X(A) = ax/az
     *  X(P) = (t1+t2)/(t1-t2)
     *  X(Q) = (t3+t4)/(t3-t4)
     *  X(P-Q) = dx
     * clobbers t1 and t2, preserves t3 and t4  */
    function mont_add ($t1, $t2, $t3, $t4, $ax, $az, $dx) {
        mul($ax, $t2, $t3);
        mul($az, $t1, $t4);
        add($t1, $ax, $az);
        sub($t2, $ax, $az);
        sqr($ax, $t1);
        sqr($t1, $t2);
        mul($az, $t1, $dx);
    }

    /* B = 2 * Q   where
     *  X(B) = bx/bz
     *  X(Q) = (t3+t4)/(t3-t4)
     * clobbers t1 and t2, preserves t3 and t4  */
    function mont_dbl ($t1, $t2, $t3, $t4, $bx, $bz) {
        sqr($t1, $t3);
        sqr($t2, $t4);
        mul($bx, $t1, $t2);
        sub($t2, $t1, $t2);
        mul_small($bz, $t2, 121665);
        add($t1, $t1, $bz);
        mul($bz, $t1, $t2);
    }

    /* Y^2 = X^3 + 486662 X^2 + X
     * t is a temporary  */
    function x_to_y2 ($t, $y2, $x) {
        sqr($t, $x);
        mul_small($y2, $x, 486662);
        add($t, $t, $y2);
        add($t, $t, $C1);
        mul($y2, $t, $x);
    }

    /* P = kG   and  s = sign(P)/k  */
    function core ($Px, $s, $k, $Gx) {
        var dx = createUnpackedArray();
        var t1 = createUnpackedArray();
        var t2 = createUnpackedArray();
        var t3 = createUnpackedArray();
        var t4 = createUnpackedArray();
        var x = [createUnpackedArray(), createUnpackedArray()];
        var z = [createUnpackedArray(), createUnpackedArray()];
        var i, j;

        /* unpack the base */
        if (Gx !== null)
            unpack(dx, Gx);
        else
            set(dx, 9);

        /* 0G = point-at-infinity */
        set(x[0], 1);
        set(z[0], 0);

        /* 1G = G */
        cpy(x[1], dx);
        set(z[1], 1);
{'fraction_non_alphanumeric': 0.2639179203344423, 'fraction_numerical': 0.1229428563017046, 'mean_word_length': 1.6166082502022108, 'pattern_counts': {'":': 0, '<': 131, '<?xml version=': 0, '>': 429, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7007012', 'n_tokens_mistral': 36901, 'n_tokens_neox': 32243, 'n_words': 7375}