wp/wp-includes/sodium_compat/src/Core32/Ed25519.php
changeset 9 177826044cd9
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     1 <?php
       
     2 
       
     3 if (class_exists('ParagonIE_Sodium_Core32_Ed25519', false)) {
       
     4     return;
       
     5 }
       
     6 
       
     7 /**
       
     8  * Class ParagonIE_Sodium_Core32_Ed25519
       
     9  */
       
    10 abstract class ParagonIE_Sodium_Core32_Ed25519 extends ParagonIE_Sodium_Core32_Curve25519
       
    11 {
       
    12     const KEYPAIR_BYTES = 96;
       
    13     const SEED_BYTES = 32;
       
    14 
       
    15     /**
       
    16      * @internal You should not use this directly from another application
       
    17      *
       
    18      * @return string (96 bytes)
       
    19      * @throws Exception
       
    20      * @throws SodiumException
       
    21      * @throws TypeError
       
    22      */
       
    23     public static function keypair()
       
    24     {
       
    25         $seed = random_bytes(self::SEED_BYTES);
       
    26         $pk = '';
       
    27         $sk = '';
       
    28         self::seed_keypair($pk, $sk, $seed);
       
    29         return $sk . $pk;
       
    30     }
       
    31 
       
    32     /**
       
    33      * @internal You should not use this directly from another application
       
    34      *
       
    35      * @param string $pk
       
    36      * @param string $sk
       
    37      * @param string $seed
       
    38      * @return string
       
    39      * @throws SodiumException
       
    40      * @throws TypeError
       
    41      */
       
    42     public static function seed_keypair(&$pk, &$sk, $seed)
       
    43     {
       
    44         if (self::strlen($seed) !== self::SEED_BYTES) {
       
    45             throw new RangeException('crypto_sign keypair seed must be 32 bytes long');
       
    46         }
       
    47 
       
    48         /** @var string $pk */
       
    49         $pk = self::publickey_from_secretkey($seed);
       
    50         $sk = $seed . $pk;
       
    51         return $sk;
       
    52     }
       
    53 
       
    54     /**
       
    55      * @internal You should not use this directly from another application
       
    56      *
       
    57      * @param string $keypair
       
    58      * @return string
       
    59      * @throws TypeError
       
    60      */
       
    61     public static function secretkey($keypair)
       
    62     {
       
    63         if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
       
    64             throw new RangeException('crypto_sign keypair must be 96 bytes long');
       
    65         }
       
    66         return self::substr($keypair, 0, 64);
       
    67     }
       
    68 
       
    69     /**
       
    70      * @internal You should not use this directly from another application
       
    71      *
       
    72      * @param string $keypair
       
    73      * @return string
       
    74      * @throws RangeException
       
    75      * @throws TypeError
       
    76      */
       
    77     public static function publickey($keypair)
       
    78     {
       
    79         if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
       
    80             throw new RangeException('crypto_sign keypair must be 96 bytes long');
       
    81         }
       
    82         return self::substr($keypair, 64, 32);
       
    83     }
       
    84 
       
    85     /**
       
    86      * @internal You should not use this directly from another application
       
    87      *
       
    88      * @param string $sk
       
    89      * @return string
       
    90      * @throws SodiumException
       
    91      * @throws TypeError
       
    92      */
       
    93     public static function publickey_from_secretkey($sk)
       
    94     {
       
    95         /** @var string $sk */
       
    96         $sk = hash('sha512', self::substr($sk, 0, 32), true);
       
    97         $sk[0] = self::intToChr(
       
    98             self::chrToInt($sk[0]) & 248
       
    99         );
       
   100         $sk[31] = self::intToChr(
       
   101             (self::chrToInt($sk[31]) & 63) | 64
       
   102         );
       
   103         return self::sk_to_pk($sk);
       
   104     }
       
   105 
       
   106     /**
       
   107      * @param string $pk
       
   108      * @return string
       
   109      * @throws SodiumException
       
   110      * @throws TypeError
       
   111      */
       
   112     public static function pk_to_curve25519($pk)
       
   113     {
       
   114         if (self::small_order($pk)) {
       
   115             throw new SodiumException('Public key is on a small order');
       
   116         }
       
   117         $A = self::ge_frombytes_negate_vartime($pk);
       
   118         $p1 = self::ge_mul_l($A);
       
   119         if (!self::fe_isnonzero($p1->X)) {
       
   120             throw new SodiumException('Unexpected zero result');
       
   121         }
       
   122 
       
   123         # fe_1(one_minus_y);
       
   124         # fe_sub(one_minus_y, one_minus_y, A.Y);
       
   125         # fe_invert(one_minus_y, one_minus_y);
       
   126         $one_minux_y = self::fe_invert(
       
   127             self::fe_sub(
       
   128                 self::fe_1(),
       
   129                 $A->Y
       
   130             )
       
   131         );
       
   132 
       
   133 
       
   134         # fe_1(x);
       
   135         # fe_add(x, x, A.Y);
       
   136         # fe_mul(x, x, one_minus_y);
       
   137         $x = self::fe_mul(
       
   138             self::fe_add(self::fe_1(), $A->Y),
       
   139             $one_minux_y
       
   140         );
       
   141 
       
   142         # fe_tobytes(curve25519_pk, x);
       
   143         return self::fe_tobytes($x);
       
   144     }
       
   145 
       
   146     /**
       
   147      * @internal You should not use this directly from another application
       
   148      *
       
   149      * @param string $sk
       
   150      * @return string
       
   151      * @throws SodiumException
       
   152      * @throws TypeError
       
   153      */
       
   154     public static function sk_to_pk($sk)
       
   155     {
       
   156         return self::ge_p3_tobytes(
       
   157             self::ge_scalarmult_base(
       
   158                 self::substr($sk, 0, 32)
       
   159             )
       
   160         );
       
   161     }
       
   162 
       
   163     /**
       
   164      * @internal You should not use this directly from another application
       
   165      *
       
   166      * @param string $message
       
   167      * @param string $sk
       
   168      * @return string
       
   169      * @throws SodiumException
       
   170      * @throws TypeError
       
   171      */
       
   172     public static function sign($message, $sk)
       
   173     {
       
   174         /** @var string $signature */
       
   175         $signature = self::sign_detached($message, $sk);
       
   176         return $signature . $message;
       
   177     }
       
   178 
       
   179     /**
       
   180      * @internal You should not use this directly from another application
       
   181      *
       
   182      * @param string $message A signed message
       
   183      * @param string $pk      Public key
       
   184      * @return string         Message (without signature)
       
   185      * @throws SodiumException
       
   186      * @throws TypeError
       
   187      */
       
   188     public static function sign_open($message, $pk)
       
   189     {
       
   190         /** @var string $signature */
       
   191         $signature = self::substr($message, 0, 64);
       
   192 
       
   193         /** @var string $message */
       
   194         $message = self::substr($message, 64);
       
   195 
       
   196         if (self::verify_detached($signature, $message, $pk)) {
       
   197             return $message;
       
   198         }
       
   199         throw new SodiumException('Invalid signature');
       
   200     }
       
   201 
       
   202     /**
       
   203      * @internal You should not use this directly from another application
       
   204      *
       
   205      * @param string $message
       
   206      * @param string $sk
       
   207      * @return string
       
   208      * @throws SodiumException
       
   209      * @throws TypeError
       
   210      */
       
   211     public static function sign_detached($message, $sk)
       
   212     {
       
   213         # crypto_hash_sha512(az, sk, 32);
       
   214         $az =  hash('sha512', self::substr($sk, 0, 32), true);
       
   215 
       
   216         # az[0] &= 248;
       
   217         # az[31] &= 63;
       
   218         # az[31] |= 64;
       
   219         $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
       
   220         $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
       
   221 
       
   222         # crypto_hash_sha512_init(&hs);
       
   223         # crypto_hash_sha512_update(&hs, az + 32, 32);
       
   224         # crypto_hash_sha512_update(&hs, m, mlen);
       
   225         # crypto_hash_sha512_final(&hs, nonce);
       
   226         $hs = hash_init('sha512');
       
   227         hash_update($hs, self::substr($az, 32, 32));
       
   228         hash_update($hs, $message);
       
   229         $nonceHash = hash_final($hs, true);
       
   230 
       
   231         # memmove(sig + 32, sk + 32, 32);
       
   232         $pk = self::substr($sk, 32, 32);
       
   233 
       
   234         # sc_reduce(nonce);
       
   235         # ge_scalarmult_base(&R, nonce);
       
   236         # ge_p3_tobytes(sig, &R);
       
   237         $nonce = self::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
       
   238         $sig = self::ge_p3_tobytes(
       
   239             self::ge_scalarmult_base($nonce)
       
   240         );
       
   241 
       
   242         # crypto_hash_sha512_init(&hs);
       
   243         # crypto_hash_sha512_update(&hs, sig, 64);
       
   244         # crypto_hash_sha512_update(&hs, m, mlen);
       
   245         # crypto_hash_sha512_final(&hs, hram);
       
   246         $hs = hash_init('sha512');
       
   247         hash_update($hs, self::substr($sig, 0, 32));
       
   248         hash_update($hs, self::substr($pk, 0, 32));
       
   249         hash_update($hs, $message);
       
   250         $hramHash = hash_final($hs, true);
       
   251 
       
   252         # sc_reduce(hram);
       
   253         # sc_muladd(sig + 32, hram, az, nonce);
       
   254         $hram = self::sc_reduce($hramHash);
       
   255         $sigAfter = self::sc_muladd($hram, $az, $nonce);
       
   256         $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
       
   257 
       
   258         try {
       
   259             ParagonIE_Sodium_Compat::memzero($az);
       
   260         } catch (SodiumException $ex) {
       
   261             $az = null;
       
   262         }
       
   263         return $sig;
       
   264     }
       
   265 
       
   266     /**
       
   267      * @internal You should not use this directly from another application
       
   268      *
       
   269      * @param string $sig
       
   270      * @param string $message
       
   271      * @param string $pk
       
   272      * @return bool
       
   273      * @throws SodiumException
       
   274      * @throws TypeError
       
   275      */
       
   276     public static function verify_detached($sig, $message, $pk)
       
   277     {
       
   278         if (self::strlen($sig) < 64) {
       
   279             throw new SodiumException('Signature is too short');
       
   280         }
       
   281         if (self::check_S_lt_L(self::substr($sig, 32, 32))) {
       
   282             throw new SodiumException('S < L - Invalid signature');
       
   283         }
       
   284         if (self::small_order($sig)) {
       
   285             throw new SodiumException('Signature is on too small of an order');
       
   286         }
       
   287         if ((self::chrToInt($sig[63]) & 224) !== 0) {
       
   288             throw new SodiumException('Invalid signature');
       
   289         }
       
   290         $d = 0;
       
   291         for ($i = 0; $i < 32; ++$i) {
       
   292             $d |= self::chrToInt($pk[$i]);
       
   293         }
       
   294         if ($d === 0) {
       
   295             throw new SodiumException('All zero public key');
       
   296         }
       
   297 
       
   298         /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
       
   299         $orig = ParagonIE_Sodium_Compat::$fastMult;
       
   300 
       
   301         // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
       
   302         ParagonIE_Sodium_Compat::$fastMult = true;
       
   303 
       
   304         /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
       
   305         $A = self::ge_frombytes_negate_vartime($pk);
       
   306 
       
   307         /** @var string $hDigest */
       
   308         $hDigest = hash(
       
   309             'sha512',
       
   310             self::substr($sig, 0, 32) .
       
   311             self::substr($pk, 0, 32) .
       
   312             $message,
       
   313             true
       
   314         );
       
   315 
       
   316         /** @var string $h */
       
   317         $h = self::sc_reduce($hDigest) . self::substr($hDigest, 32);
       
   318 
       
   319         /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
       
   320         $R = self::ge_double_scalarmult_vartime(
       
   321             $h,
       
   322             $A,
       
   323             self::substr($sig, 32)
       
   324         );
       
   325 
       
   326         /** @var string $rcheck */
       
   327         $rcheck = self::ge_tobytes($R);
       
   328 
       
   329         // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
       
   330         ParagonIE_Sodium_Compat::$fastMult = $orig;
       
   331 
       
   332         return self::verify_32($rcheck, self::substr($sig, 0, 32));
       
   333     }
       
   334 
       
   335     /**
       
   336      * @internal You should not use this directly from another application
       
   337      *
       
   338      * @param string $S
       
   339      * @return bool
       
   340      * @throws SodiumException
       
   341      * @throws TypeError
       
   342      */
       
   343     public static function check_S_lt_L($S)
       
   344     {
       
   345         if (self::strlen($S) < 32) {
       
   346             throw new SodiumException('Signature must be 32 bytes');
       
   347         }
       
   348         static $L = array(
       
   349             0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
       
   350             0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
       
   351             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   352             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
       
   353         );
       
   354         /** @var array<int, int> $L */
       
   355         $c = 0;
       
   356         $n = 1;
       
   357         $i = 32;
       
   358 
       
   359         do {
       
   360             --$i;
       
   361             $x = self::chrToInt($S[$i]);
       
   362             $c |= (
       
   363                 (($x - $L[$i]) >> 8) & $n
       
   364             );
       
   365             $n &= (
       
   366                 (($x ^ $L[$i]) - 1) >> 8
       
   367             );
       
   368         } while ($i !== 0);
       
   369 
       
   370         return $c === 0;
       
   371     }
       
   372 
       
   373     /**
       
   374      * @param string $R
       
   375      * @return bool
       
   376      * @throws SodiumException
       
   377      * @throws TypeError
       
   378      */
       
   379     public static function small_order($R)
       
   380     {
       
   381         static $blacklist = array(
       
   382             /* 0 (order 4) */
       
   383             array(
       
   384                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   385                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   386                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   387                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
       
   388             ),
       
   389             /* 1 (order 1) */
       
   390             array(
       
   391                 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   392                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   393                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       
   394                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
       
   395             ),
       
   396             /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
       
   397             array(
       
   398                 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
       
   399                 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
       
   400                 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
       
   401                 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05
       
   402             ),
       
   403             /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
       
   404             array(
       
   405                 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
       
   406                 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
       
   407                 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
       
   408                 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a
       
   409             ),
       
   410             /* p-1 (order 2) */
       
   411             array(
       
   412                 0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
       
   413                 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
       
   414                 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
       
   415                 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85
       
   416             ),
       
   417             /* p (order 4) */
       
   418             array(
       
   419                 0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
       
   420                 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
       
   421                 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
       
   422                 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa
       
   423             ),
       
   424             /* p+1 (order 1) */
       
   425             array(
       
   426                 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   427                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   428                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   429                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
       
   430             ),
       
   431             /* p+2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
       
   432             array(
       
   433                 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   434                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   435                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   436                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
       
   437             ),
       
   438             /* p+55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
       
   439             array(
       
   440                 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   441                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   442                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   443                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
       
   444             ),
       
   445             /* 2p-1 (order 2) */
       
   446             array(
       
   447                 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   448                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   449                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   450                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
       
   451             ),
       
   452             /* 2p (order 4) */
       
   453             array(
       
   454                 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   455                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   456                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   457                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
       
   458             ),
       
   459             /* 2p+1 (order 1) */
       
   460             array(
       
   461                 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   462                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   463                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       
   464                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
       
   465             )
       
   466         );
       
   467         /** @var array<int, array<int, int>> $blacklist */
       
   468         $countBlacklist = count($blacklist);
       
   469 
       
   470         for ($i = 0; $i < $countBlacklist; ++$i) {
       
   471             $c = 0;
       
   472             for ($j = 0; $j < 32; ++$j) {
       
   473                 $c |= self::chrToInt($R[$j]) ^ $blacklist[$i][$j];
       
   474             }
       
   475             if ($c === 0) {
       
   476                 return true;
       
   477             }
       
   478         }
       
   479         return false;
       
   480     }
       
   481 }