1<?php
2
3/**
4 * Class ParagonIE_Sodium_Core_Ristretto255
5 */
6class ParagonIE_Sodium_Core_Ristretto255 extends ParagonIE_Sodium_Core_Ed25519
7{
8    const crypto_core_ristretto255_HASHBYTES = 64;
9    const HASH_SC_L = 48;
10    const CORE_H2C_SHA256 = 1;
11    const CORE_H2C_SHA512 = 2;
12
13    /**
14     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
15     * @param int $b
16     * @return ParagonIE_Sodium_Core_Curve25519_Fe
17     */
18    public static function fe_cneg(ParagonIE_Sodium_Core_Curve25519_Fe $f, $b)
19    {
20        $negf = self::fe_neg($f);
21        return self::fe_cmov($f, $negf, $b);
22    }
23
24    /**
25     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
26     * @return ParagonIE_Sodium_Core_Curve25519_Fe
27     * @throws SodiumException
28     */
29    public static function fe_abs(ParagonIE_Sodium_Core_Curve25519_Fe $f)
30    {
31        return self::fe_cneg($f, self::fe_isnegative($f));
32    }
33
34    /**
35     * Returns 0 if this field element results in all NUL bytes.
36     *
37     * @internal You should not use this directly from another application
38     *
39     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
40     * @return int
41     * @throws SodiumException
42     */
43    public static function fe_iszero(ParagonIE_Sodium_Core_Curve25519_Fe $f)
44    {
45        static $zero;
46        if ($zero === null) {
47            $zero = str_repeat("\x00", 32);
48        }
49        /** @var string $zero */
50        $str = self::fe_tobytes($f);
51
52        $d = 0;
53        for ($i = 0; $i < 32; ++$i) {
54            $d |= self::chrToInt($str[$i]);
55        }
56        return (($d - 1) >> 31) & 1;
57    }
58
59
60    /**
61     * @param ParagonIE_Sodium_Core_Curve25519_Fe $u
62     * @param ParagonIE_Sodium_Core_Curve25519_Fe $v
63     * @return array{x: ParagonIE_Sodium_Core_Curve25519_Fe, nonsquare: int}
64     *
65     * @throws SodiumException
66     */
67    public static function ristretto255_sqrt_ratio_m1(
68        ParagonIE_Sodium_Core_Curve25519_Fe $u,
69        ParagonIE_Sodium_Core_Curve25519_Fe $v
70    ) {
71        $sqrtm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1);
72
73        $v3 = self::fe_mul(
74            self::fe_sq($v),
75            $v
76        ); /* v3 = v^3 */
77        $x = self::fe_mul(
78            self::fe_mul(
79                self::fe_sq($v3),
80                $u
81            ),
82            $v
83        ); /* x = uv^7 */
84
85        $x = self::fe_mul(
86            self::fe_mul(
87                self::fe_pow22523($x), /* x = (uv^7)^((q-5)/8) */
88                $v3
89            ),
90            $u
91        ); /* x = uv^3(uv^7)^((q-5)/8) */
92
93        $vxx = self::fe_mul(
94            self::fe_sq($x),
95            $v
96        ); /* vx^2 */
97
98        $m_root_check = self::fe_sub($vxx, $u); /* vx^2-u */
99        $p_root_check = self::fe_add($vxx, $u); /* vx^2+u */
100        $f_root_check = self::fe_mul($u, $sqrtm1); /* u*sqrt(-1) */
101        $f_root_check = self::fe_add($vxx, $f_root_check); /* vx^2+u*sqrt(-1) */
102
103        $has_m_root = self::fe_iszero($m_root_check);
104        $has_p_root = self::fe_iszero($p_root_check);
105        $has_f_root = self::fe_iszero($f_root_check);
106
107        $x_sqrtm1 = self::fe_mul($x, $sqrtm1); /* x*sqrt(-1) */
108
109        $x = self::fe_abs(
110            self::fe_cmov($x, $x_sqrtm1, $has_p_root | $has_f_root)
111        );
112        return array(
113            'x' => $x,
114            'nonsquare' => $has_m_root | $has_p_root
115        );
116    }
117
118    /**
119     * @param string $s
120     * @return int
121     * @throws SodiumException
122     */
123    public static function ristretto255_point_is_canonical($s)
124    {
125        $c = (self::chrToInt($s[31]) & 0x7f) ^ 0x7f;
126        for ($i = 30; $i > 0; --$i) {
127            $c |= self::chrToInt($s[$i]) ^ 0xff;
128        }
129        $c = ($c - 1) >> 8;
130        $d = (0xed - 1 - self::chrToInt($s[0])) >> 8;
131        $e = self::chrToInt($s[31]) >> 7;
132
133        return 1 - ((($c & $d) | $e | self::chrToInt($s[0])) & 1);
134    }
135
136    /**
137     * @param string $s
138     * @param bool $skipCanonicalCheck
139     * @return array{h: ParagonIE_Sodium_Core_Curve25519_Ge_P3, res: int}
140     * @throws SodiumException
141     */
142    public static function ristretto255_frombytes($s, $skipCanonicalCheck = false)
143    {
144        if (!$skipCanonicalCheck) {
145            if (!self::ristretto255_point_is_canonical($s)) {
146                throw new SodiumException('S is not canonical');
147            }
148        }
149
150        $s_ = self::fe_frombytes($s);
151        $ss = self::fe_sq($s_); /* ss = s^2 */
152
153        $u1 = self::fe_sub(self::fe_1(), $ss); /* u1 = 1-ss */
154        $u1u1 = self::fe_sq($u1); /* u1u1 = u1^2 */
155
156        $u2 = self::fe_add(self::fe_1(), $ss); /* u2 = 1+ss */
157        $u2u2 = self::fe_sq($u2); /* u2u2 = u2^2 */
158
159        $v = self::fe_mul(
160            ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d),
161            $u1u1
162        ); /* v = d*u1^2 */
163        $v = self::fe_neg($v); /* v = -d*u1^2 */
164        $v = self::fe_sub($v, $u2u2); /* v = -(d*u1^2)-u2^2 */
165        $v_u2u2 = self::fe_mul($v, $u2u2); /* v_u2u2 = v*u2^2 */
166
167        // fe25519_1(one);
168        // notsquare = ristretto255_sqrt_ratio_m1(inv_sqrt, one, v_u2u2);
169        $one = self::fe_1();
170        $result = self::ristretto255_sqrt_ratio_m1($one, $v_u2u2);
171        $inv_sqrt = $result['x'];
172        $notsquare = $result['nonsquare'];
173
174        $h = new ParagonIE_Sodium_Core_Curve25519_Ge_P3();
175
176        $h->X = self::fe_mul($inv_sqrt, $u2);
177        $h->Y = self::fe_mul(self::fe_mul($inv_sqrt, $h->X), $v);
178
179        $h->X = self::fe_mul($h->X, $s_);
180        $h->X = self::fe_abs(
181            self::fe_add($h->X, $h->X)
182        );
183        $h->Y = self::fe_mul($u1, $h->Y);
184        $h->Z = self::fe_1();
185        $h->T = self::fe_mul($h->X, $h->Y);
186
187        $res = - ((1 - $notsquare) | self::fe_isnegative($h->T) | self::fe_iszero($h->Y));
188        return array('h' => $h, 'res' => $res);
189    }
190
191    /**
192     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h
193     * @return string
194     * @throws SodiumException
195     */
196    public static function ristretto255_p3_tobytes(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h)
197    {
198        $sqrtm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1);
199        $invsqrtamd = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$invsqrtamd);
200
201        $u1 = self::fe_add($h->Z, $h->Y); /* u1 = Z+Y */
202        $zmy = self::fe_sub($h->Z, $h->Y); /* zmy = Z-Y */
203        $u1 = self::fe_mul($u1, $zmy); /* u1 = (Z+Y)*(Z-Y) */
204        $u2 = self::fe_mul($h->X, $h->Y); /* u2 = X*Y */
205
206        $u1_u2u2 = self::fe_mul(self::fe_sq($u2), $u1); /* u1_u2u2 = u1*u2^2 */
207        $one = self::fe_1();
208
209        // fe25519_1(one);
210        // (void) ristretto255_sqrt_ratio_m1(inv_sqrt, one, u1_u2u2);
211        $result = self::ristretto255_sqrt_ratio_m1($one, $u1_u2u2);
212        $inv_sqrt = $result['x'];
213
214        $den1 = self::fe_mul($inv_sqrt, $u1); /* den1 = inv_sqrt*u1 */
215        $den2 = self::fe_mul($inv_sqrt, $u2); /* den2 = inv_sqrt*u2 */
216        $z_inv = self::fe_mul($h->T, self::fe_mul($den1, $den2)); /* z_inv = den1*den2*T */
217
218        $ix = self::fe_mul($h->X, $sqrtm1); /* ix = X*sqrt(-1) */
219        $iy = self::fe_mul($h->Y, $sqrtm1); /* iy = Y*sqrt(-1) */
220        $eden = self::fe_mul($den1, $invsqrtamd);
221
222        $t_z_inv =  self::fe_mul($h->T, $z_inv); /* t_z_inv = T*z_inv */
223        $rotate = self::fe_isnegative($t_z_inv);
224
225        $x_ = self::fe_copy($h->X);
226        $y_ = self::fe_copy($h->Y);
227        $den_inv = self::fe_copy($den2);
228
229        $x_ = self::fe_cmov($x_, $iy, $rotate);
230        $y_ = self::fe_cmov($y_, $ix, $rotate);
231        $den_inv = self::fe_cmov($den_inv, $eden, $rotate);
232
233        $x_z_inv = self::fe_mul($x_, $z_inv);
234        $y_ = self::fe_cneg($y_, self::fe_isnegative($x_z_inv));
235
236
237        // fe25519_sub(s_, h->Z, y_);
238        // fe25519_mul(s_, den_inv, s_);
239        // fe25519_abs(s_, s_);
240        // fe25519_tobytes(s, s_);
241        return self::fe_tobytes(
242            self::fe_abs(
243                self::fe_mul(
244                    $den_inv,
245                    self::fe_sub($h->Z, $y_)
246                )
247            )
248        );
249    }
250
251    /**
252     * @param ParagonIE_Sodium_Core_Curve25519_Fe $t
253     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3
254     *
255     * @throws SodiumException
256     */
257    public static function ristretto255_elligator(ParagonIE_Sodium_Core_Curve25519_Fe $t)
258    {
259        $sqrtm1   = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1);
260        $onemsqd  = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$onemsqd);
261        $d        = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d);
262        $sqdmone  = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqdmone);
263        $sqrtadm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtadm1);
264
265        $one = self::fe_1();
266        $r   = self::fe_mul($sqrtm1, self::fe_sq($t));         /* r = sqrt(-1)*t^2 */
267        $u   = self::fe_mul(self::fe_add($r, $one), $onemsqd); /* u = (r+1)*(1-d^2) */
268        $c   = self::fe_neg(self::fe_1());                     /* c = -1 */
269        $rpd = self::fe_add($r, $d);                           /* rpd = r+d */
270
271        $v = self::fe_mul(
272            self::fe_sub(
273                $c,
274                self::fe_mul($r, $d)
275            ),
276            $rpd
277        ); /* v = (c-r*d)*(r+d) */
278
279        $result = self::ristretto255_sqrt_ratio_m1($u, $v);
280        $s = $result['x'];
281        $wasnt_square = 1 - $result['nonsquare'];
282
283        $s_prime = self::fe_neg(
284            self::fe_abs(
285                self::fe_mul($s, $t)
286            )
287        ); /* s_prime = -|s*t| */
288        $s = self::fe_cmov($s, $s_prime, $wasnt_square);
289        $c = self::fe_cmov($c, $r, $wasnt_square);
290
291        // fe25519_sub(n, r, one);            /* n = r-1 */
292        // fe25519_mul(n, n, c);              /* n = c*(r-1) */
293        // fe25519_mul(n, n, ed25519_sqdmone); /* n = c*(r-1)*(d-1)^2 */
294        // fe25519_sub(n, n, v);              /* n =  c*(r-1)*(d-1)^2-v */
295        $n = self::fe_sub(
296            self::fe_mul(
297                self::fe_mul(
298                    self::fe_sub($r, $one),
299                    $c
300                ),
301                $sqdmone
302            ),
303            $v
304        ); /* n =  c*(r-1)*(d-1)^2-v */
305
306        $w0 = self::fe_mul(
307            self::fe_add($s, $s),
308            $v
309        ); /* w0 = 2s*v */
310
311        $w1 = self::fe_mul($n, $sqrtadm1); /* w1 = n*sqrt(ad-1) */
312        $ss = self::fe_sq($s); /* ss = s^2 */
313        $w2 = self::fe_sub($one, $ss); /* w2 = 1-s^2 */
314        $w3 = self::fe_add($one, $ss); /* w3 = 1+s^2 */
315
316        return new ParagonIE_Sodium_Core_Curve25519_Ge_P3(
317            self::fe_mul($w0, $w3),
318            self::fe_mul($w2, $w1),
319            self::fe_mul($w1, $w3),
320            self::fe_mul($w0, $w2)
321        );
322    }
323
324    /**
325     * @param string $h
326     * @return string
327     * @throws SodiumException
328     */
329    public static function ristretto255_from_hash($h)
330    {
331        if (self::strlen($h) !== 64) {
332            throw new SodiumException('Hash must be 64 bytes');
333        }
334        //fe25519_frombytes(r0, h);
335        //fe25519_frombytes(r1, h + 32);
336        $r0 = self::fe_frombytes(self::substr($h, 0, 32));
337        $r1 = self::fe_frombytes(self::substr($h, 32, 32));
338
339        //ristretto255_elligator(&p0, r0);
340        //ristretto255_elligator(&p1, r1);
341        $p0 = self::ristretto255_elligator($r0);
342        $p1 = self::ristretto255_elligator($r1);
343
344        //ge25519_p3_to_cached(&p1_cached, &p1);
345        //ge25519_add_cached(&p_p1p1, &p0, &p1_cached);
346        $p_p1p1 = self::ge_add(
347            $p0,
348            self::ge_p3_to_cached($p1)
349        );
350
351        //ge25519_p1p1_to_p3(&p, &p_p1p1);
352        //ristretto255_p3_tobytes(s, &p);
353        return self::ristretto255_p3_tobytes(
354            self::ge_p1p1_to_p3($p_p1p1)
355        );
356    }
357
358    /**
359     * @param string $p
360     * @return int
361     * @throws SodiumException
362     */
363    public static function is_valid_point($p)
364    {
365        $result = self::ristretto255_frombytes($p);
366        if ($result['res'] !== 0) {
367            return 0;
368        }
369        return 1;
370    }
371
372    /**
373     * @param string $p
374     * @param string $q
375     * @return string
376     * @throws SodiumException
377     */
378    public static function ristretto255_add($p, $q)
379    {
380        $p_res = self::ristretto255_frombytes($p);
381        $q_res = self::ristretto255_frombytes($q);
382        if ($p_res['res'] !== 0 || $q_res['res'] !== 0) {
383            throw new SodiumException('Could not add points');
384        }
385        $p_p3 = $p_res['h'];
386        $q_p3 = $q_res['h'];
387        $q_cached = self::ge_p3_to_cached($q_p3);
388        $r_p1p1 = self::ge_add($p_p3, $q_cached);
389        $r_p3 = self::ge_p1p1_to_p3($r_p1p1);
390        return self::ristretto255_p3_tobytes($r_p3);
391    }
392
393    /**
394     * @param string $p
395     * @param string $q
396     * @return string
397     * @throws SodiumException
398     */
399    public static function ristretto255_sub($p, $q)
400    {
401        $p_res = self::ristretto255_frombytes($p);
402        $q_res = self::ristretto255_frombytes($q);
403        if ($p_res['res'] !== 0 || $q_res['res'] !== 0) {
404            throw new SodiumException('Could not add points');
405        }
406        $p_p3 = $p_res['h'];
407        $q_p3 = $q_res['h'];
408        $q_cached = self::ge_p3_to_cached($q_p3);
409        $r_p1p1 = self::ge_sub($p_p3, $q_cached);
410        $r_p3 = self::ge_p1p1_to_p3($r_p1p1);
411        return self::ristretto255_p3_tobytes($r_p3);
412    }
413
414
415    /**
416     * @param int $hLen
417     * @param ?string $ctx
418     * @param string $msg
419     * @return string
420     * @throws SodiumException
421     * @psalm-suppress PossiblyInvalidArgument hash API
422     */
423    protected static function h2c_string_to_hash_sha256($hLen, $ctx, $msg)
424    {
425        $h = array_fill(0, $hLen, 0);
426        $ctx_len = !is_null($ctx) ? self::strlen($ctx) : 0;
427        if ($hLen > 0xff) {
428            throw new SodiumException('Hash must be less than 256 bytes');
429        }
430
431        if ($ctx_len > 0xff) {
432            $st = hash_init('sha256');
433            self::hash_update($st, "H2C-OVERSIZE-DST-");
434            self::hash_update($st, $ctx);
435            $ctx = hash_final($st, true);
436            $ctx_len = 32;
437        }
438        $t = array(0, $hLen, 0);
439        $ux = str_repeat("\0", 64);
440        $st = hash_init('sha256');
441        self::hash_update($st, $ux);
442        self::hash_update($st, $msg);
443        self::hash_update($st, self::intArrayToString($t));
444        self::hash_update($st, $ctx);
445        self::hash_update($st, self::intToChr($ctx_len));
446        $u0 = hash_final($st, true);
447
448        for ($i = 0; $i < $hLen; $i += 64) {
449            $ux = self::xorStrings($ux, $u0);
450            ++$t[2];
451            $st = hash_init('sha256');
452            self::hash_update($st, $ux);
453            self::hash_update($st, self::intToChr($t[2]));
454            self::hash_update($st, $ctx);
455            self::hash_update($st, self::intToChr($ctx_len));
456            $ux = hash_final($st, true);
457            $amount = min($hLen - $i, 64);
458            for ($j = 0; $j < $amount; ++$j) {
459                $h[$i + $j] = self::chrToInt($ux[$i]);
460            }
461        }
462        return self::intArrayToString(array_slice($h, 0, $hLen));
463    }
464
465    /**
466     * @param int $hLen
467     * @param ?string $ctx
468     * @param string $msg
469     * @return string
470     * @throws SodiumException
471     * @psalm-suppress PossiblyInvalidArgument hash API
472     */
473    protected static function h2c_string_to_hash_sha512($hLen, $ctx, $msg)
474    {
475        $h = array_fill(0, $hLen, 0);
476        $ctx_len = !is_null($ctx) ? self::strlen($ctx) : 0;
477        if ($hLen > 0xff) {
478            throw new SodiumException('Hash must be less than 256 bytes');
479        }
480
481        if ($ctx_len > 0xff) {
482            $st = hash_init('sha256');
483            self::hash_update($st, "H2C-OVERSIZE-DST-");
484            self::hash_update($st, $ctx);
485            $ctx = hash_final($st, true);
486            $ctx_len = 32;
487        }
488        $t = array(0, $hLen, 0);
489        $ux = str_repeat("\0", 128);
490        $st = hash_init('sha512');
491        self::hash_update($st, $ux);
492        self::hash_update($st, $msg);
493        self::hash_update($st, self::intArrayToString($t));
494        self::hash_update($st, $ctx);
495        self::hash_update($st, self::intToChr($ctx_len));
496        $u0 = hash_final($st, true);
497
498        for ($i = 0; $i < $hLen; $i += 128) {
499            $ux = self::xorStrings($ux, $u0);
500            ++$t[2];
501            $st = hash_init('sha512');
502            self::hash_update($st, $ux);
503            self::hash_update($st, self::intToChr($t[2]));
504            self::hash_update($st, $ctx);
505            self::hash_update($st, self::intToChr($ctx_len));
506            $ux = hash_final($st, true);
507            $amount = min($hLen - $i, 128);
508            for ($j = 0; $j < $amount; ++$j) {
509                $h[$i + $j] = self::chrToInt($ux[$i]);
510            }
511        }
512        return self::intArrayToString(array_slice($h, 0, $hLen));
513    }
514
515    /**
516     * @param int $hLen
517     * @param ?string $ctx
518     * @param string $msg
519     * @param int $hash_alg
520     * @return string
521     * @throws SodiumException
522     */
523    public static function h2c_string_to_hash($hLen, $ctx, $msg, $hash_alg)
524    {
525        switch ($hash_alg) {
526            case self::CORE_H2C_SHA256:
527                return self::h2c_string_to_hash_sha256($hLen, $ctx, $msg);
528            case self::CORE_H2C_SHA512:
529                return self::h2c_string_to_hash_sha512($hLen, $ctx, $msg);
530            default:
531                throw new SodiumException('Invalid H2C hash algorithm');
532        }
533    }
534
535    /**
536     * @param ?string $ctx
537     * @param string $msg
538     * @param int $hash_alg
539     * @return string
540     * @throws SodiumException
541     */
542    protected static function _string_to_element($ctx, $msg, $hash_alg)
543    {
544        return self::ristretto255_from_hash(
545            self::h2c_string_to_hash(self::crypto_core_ristretto255_HASHBYTES, $ctx, $msg, $hash_alg)
546        );
547    }
548
549    /**
550     * @return string
551     * @throws SodiumException
552     * @throws Exception
553     */
554    public static function ristretto255_random()
555    {
556        return self::ristretto255_from_hash(
557            ParagonIE_Sodium_Compat::randombytes_buf(self::crypto_core_ristretto255_HASHBYTES)
558        );
559    }
560
561    /**
562     * @return string
563     * @throws SodiumException
564     */
565    public static function ristretto255_scalar_random()
566    {
567        return self::scalar_random();
568    }
569
570    /**
571     * @param string $s
572     * @return string
573     * @throws SodiumException
574     */
575    public static function ristretto255_scalar_complement($s)
576    {
577        return self::scalar_complement($s);
578    }
579
580
581    /**
582     * @param string $s
583     * @return string
584     */
585    public static function ristretto255_scalar_invert($s)
586    {
587        return self::sc25519_invert($s);
588    }
589
590    /**
591     * @param string $s
592     * @return string
593     * @throws SodiumException
594     */
595    public static function ristretto255_scalar_negate($s)
596    {
597        return self::scalar_negate($s);
598    }
599
600    /**
601     * @param string $x
602     * @param string $y
603     * @return string
604     */
605    public static function ristretto255_scalar_add($x, $y)
606    {
607        return self::scalar_add($x, $y);
608    }
609
610    /**
611     * @param string $x
612     * @param string $y
613     * @return string
614     */
615    public static function ristretto255_scalar_sub($x, $y)
616    {
617        return self::scalar_sub($x, $y);
618    }
619
620    /**
621     * @param string $x
622     * @param string $y
623     * @return string
624     */
625    public static function ristretto255_scalar_mul($x, $y)
626    {
627        return self::sc25519_mul($x, $y);
628    }
629
630    /**
631     * @param string $ctx
632     * @param string $msg
633     * @param int $hash_alg
634     * @return string
635     * @throws SodiumException
636     */
637    public static function ristretto255_scalar_from_string($ctx, $msg, $hash_alg)
638    {
639        $h = array_fill(0, 64, 0);
640        $h_be = self::stringToIntArray(
641            self::h2c_string_to_hash(
642                self::HASH_SC_L, $ctx, $msg, $hash_alg
643            )
644        );
645
646        for ($i = 0; $i < self::HASH_SC_L; ++$i) {
647            $h[$i] = $h_be[self::HASH_SC_L - 1 - $i];
648        }
649        return self::ristretto255_scalar_reduce(self::intArrayToString($h));
650    }
651
652    /**
653     * @param string $s
654     * @return string
655     */
656    public static function ristretto255_scalar_reduce($s)
657    {
658        return self::sc_reduce($s);
659    }
660
661    /**
662     * @param string $n
663     * @param string $p
664     * @return string
665     * @throws SodiumException
666     */
667    public static function scalarmult_ristretto255($n, $p)
668    {
669        if (self::strlen($n) !== 32) {
670            throw new SodiumException('Scalar must be 32 bytes, ' . self::strlen($p) . ' given.');
671        }
672        if (self::strlen($p) !== 32) {
673            throw new SodiumException('Point must be 32 bytes, ' . self::strlen($p) . ' given.');
674        }
675        $result = self::ristretto255_frombytes($p);
676        if ($result['res'] !== 0) {
677            throw new SodiumException('Could not multiply points');
678        }
679        $P = $result['h'];
680
681        $t = self::stringToIntArray($n);
682        $t[31] &= 0x7f;
683        $Q = self::ge_scalarmult(self::intArrayToString($t), $P);
684        $q = self::ristretto255_p3_tobytes($Q);
685        if (ParagonIE_Sodium_Compat::is_zero($q)) {
686            throw new SodiumException('An unknown error has occurred');
687        }
688        return $q;
689    }
690
691    /**
692     * @param string $n
693     * @return string
694     * @throws SodiumException
695     */
696    public static function scalarmult_ristretto255_base($n)
697    {
698        $t = self::stringToIntArray($n);
699        $t[31] &= 0x7f;
700        $Q = self::ge_scalarmult_base(self::intArrayToString($t));
701        $q = self::ristretto255_p3_tobytes($Q);
702        if (ParagonIE_Sodium_Compat::is_zero($q)) {
703            throw new SodiumException('An unknown error has occurred');
704        }
705        return $q;
706    }
707}
708