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