From 82072a76b3fdcf5451a7034bd2342be8ee6de96a Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Thu, 9 May 2019 14:47:36 -0400 Subject: [PATCH] 32-bit curve25519 speedup (important for WordPress) Makes `ParagonIE_Sodium_Compat::$fastMult` actually do something on 32-bit platforms. --- src/Core32/Int64.php | 149 +++++++++++++++++++++++++++++++++++++-- tests/Int64TestTemp.php | 46 ++++++++++++ tests/Windows32Test.php | 1 + tests/unit/Int64Test.php | 4 +- 4 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 tests/Int64TestTemp.php diff --git a/src/Core32/Int64.php b/src/Core32/Int64.php index cc7b69f8..c620237f 100644 --- a/src/Core32/Int64.php +++ b/src/Core32/Int64.php @@ -205,6 +205,9 @@ public function mask64($hi = 0, $lo = 0) */ public function mulInt($int = 0, $size = 0) { + if (ParagonIE_Sodium_Compat::$fastMult) { + return $this->mulIntFast($int); + } ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1); ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2); /** @var int $int */ @@ -268,11 +271,11 @@ public function mulInt($int = 0, $size = 0) $a3 &= 0xffff; $int >>= 1; - $return->limbs[0] = $ret0; - $return->limbs[1] = $ret1; - $return->limbs[2] = $ret2; - $return->limbs[3] = $ret3; } + $return->limbs[0] = $ret0; + $return->limbs[1] = $ret1; + $return->limbs[2] = $ret2; + $return->limbs[3] = $ret3; return $return; } @@ -317,6 +320,141 @@ public static function ctSelect( ); } + /** + * @param array $a + * @param array $b + * @param int $baseLog2 + * @return array + */ + public function multiplyLong(array $a, array $b, $baseLog2 = 16) + { + $a_l = count($a); + $b_l = count($b); + $r = array_fill(0, $a_l + $b_l + 1, 0); + $base = 1 << $baseLog2; + for ($i = 0; $i < $a_l; ++$i) { + $a_i = $a[$i]; + for ($j = 0; $j < $a_l; ++$j) { + $b_j = $b[$j]; + $product = ($a_i * $b_j) + $r[$i + $j]; + $carry = ($product >> $baseLog2 & 0xffff); + $r[$i + $j] = ($product - (int) ($carry * $base)) & 0xffff; + $r[$i + $j + 1] += $carry; + } + } + return array_slice($r, 0, 5); + } + + /** + * @param int $int + * @return ParagonIE_Sodium_Core32_Int64 + */ + public function mulIntFast($int) + { + // Handle negative numbers + $aNeg = ($this->limbs[0] >> 15) & 1; + $bNeg = ($int >> 31) & 1; + $a = array_reverse($this->limbs); + $b = array( + $int & 0xffff, + ($int >> 16) & 0xffff, + -$bNeg & 0xffff, + -$bNeg & 0xffff + ); + if ($aNeg) { + for ($i = 0; $i < 4; ++$i) { + $a[$i] = ($a[$i] ^ 0xffff) & 0xffff; + } + ++$a[0]; + } + if ($bNeg) { + for ($i = 0; $i < 4; ++$i) { + $b[$i] = ($b[$i] ^ 0xffff) & 0xffff; + } + ++$b[0]; + } + // Multiply + $res = $this->multiplyLong($a, $b); + + // Re-apply negation to results + if ($aNeg !== $bNeg) { + for ($i = 0; $i < 4; ++$i) { + $res[$i] = (0xffff ^ $res[$i]) & 0xffff; + } + // Handle integer overflow + $c = 1; + for ($i = 0; $i < 4; ++$i) { + $res[$i] += $c; + $c = $res[$i] >> 16; + $res[$i] &= 0xffff; + } + } + + // Return our values + $return = new ParagonIE_Sodium_Core32_Int64(); + $return->limbs = array( + $res[3] & 0xffff, + $res[2] & 0xffff, + $res[1] & 0xffff, + $res[0] & 0xffff + ); + if (count($res) > 4) { + $return->overflow = $res[4] & 0xffff; + } + $return->unsignedInt = $this->unsignedInt; + return $return; + } + + /** + * @param ParagonIE_Sodium_Core32_Int64 $right + * @return ParagonIE_Sodium_Core32_Int64 + */ + public function mulInt64Fast(ParagonIE_Sodium_Core32_Int64 $right) + { + $aNeg = ($this->limbs[0] >> 15) & 1; + $bNeg = ($right->limbs[0] >> 15) & 1; + + $a = array_reverse($this->limbs); + $b = array_reverse($right->limbs); + if ($aNeg) { + for ($i = 0; $i < 4; ++$i) { + $a[$i] = ($a[$i] ^ 0xffff) & 0xffff; + } + ++$a[0]; + } + if ($bNeg) { + for ($i = 0; $i < 4; ++$i) { + $b[$i] = ($b[$i] ^ 0xffff) & 0xffff; + } + ++$b[0]; + } + $res = $this->multiplyLong($a, $b); + if ($aNeg !== $bNeg) { + if ($aNeg !== $bNeg) { + for ($i = 0; $i < 4; ++$i) { + $res[$i] = ($res[$i] ^ 0xffff) & 0xffff; + } + $c = 1; + for ($i = 0; $i < 4; ++$i) { + $res[$i] += $c; + $c = $res[$i] >> 16; + $res[$i] &= 0xffff; + } + } + } + $return = new ParagonIE_Sodium_Core32_Int64(); + $return->limbs = array( + $res[3] & 0xffff, + $res[2] & 0xffff, + $res[1] & 0xffff, + $res[0] & 0xffff + ); + if (count($res) > 4) { + $return->overflow = $res[4]; + } + return $return; + } + /** * @param ParagonIE_Sodium_Core32_Int64 $int * @param int $size @@ -327,6 +465,9 @@ public static function ctSelect( */ public function mulInt64(ParagonIE_Sodium_Core32_Int64 $int, $size = 0) { + if (ParagonIE_Sodium_Compat::$fastMult) { + return $this->mulInt64Fast($int); + } ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2); if (!$size) { $size = 63; diff --git a/tests/Int64TestTemp.php b/tests/Int64TestTemp.php new file mode 100644 index 00000000..7ce5f256 --- /dev/null +++ b/tests/Int64TestTemp.php @@ -0,0 +1,46 @@ +markTestSkipped('Only relevant to 32-bit platforms.'); + } + ParagonIE_Sodium_Compat::$fastMult = true; + } + + + /** + * @covers ParagonIE_Sodium_Core32_Int64::mulInt() + * @covers ParagonIE_Sodium_Core32_Int64::mulInt64() + * @throws SodiumException + * @throws TypeError + */ + public function testMult() + { + for ($j = 0; $j < 64; ++$j) { + $baseSmall = random_int(1, 65536); + $base = new ParagonIE_Sodium_Core32_Int64(array(0, 0, 0, $baseSmall)); + for ($i = 0; $i < 64; ++$i) { + $value = random_int(1, 65536); + $result = ($baseSmall * $value); + $expected = array( + 0, + 0, + ($result >> 16) & 0xffff, + $result & 0xffff + ); + $this->assertSame( + $expected, + $base->mulInt($value)->limbs, + $baseSmall . ' x ' . $value . ' = ' . $result + ); + } + } + } + +} diff --git a/tests/Windows32Test.php b/tests/Windows32Test.php index 5392d2ca..e05682a3 100644 --- a/tests/Windows32Test.php +++ b/tests/Windows32Test.php @@ -7,6 +7,7 @@ public function setUp() if (PHP_INT_SIZE !== 4) { $this->markTestSkipped('64-bit OS'); } + ParagonIE_Sodium_Compat::$fastMult = true; ParagonIE_Sodium_Compat::$disableFallbackForUnitTests = true; } diff --git a/tests/unit/Int64Test.php b/tests/unit/Int64Test.php index c8069fc7..39cfe509 100644 --- a/tests/unit/Int64Test.php +++ b/tests/unit/Int64Test.php @@ -10,6 +10,7 @@ public function setUp() if (PHP_INT_SIZE === 8) { $this->markTestSkipped('Only relevant to 32-bit platforms.'); } + ParagonIE_Sodium_Compat::$fastMult = true; } /** @@ -195,7 +196,7 @@ public function testMult() $negOne = new ParagonIE_Sodium_Core32_Int64(array(0xffff, 0xffff, 0xffff, -1 & 0xffff)); $this->assertSame( - array(0xffff, 0xffff, 0xffff, -5 & 0xffff), + array(0xffff, 0xffff, 0xffff, 0xfffb), $negOne->mulInt(5)->limbs ); $this->assertSame( @@ -226,7 +227,6 @@ public function testMult() ($result >> 16) & 0xffff, $result & 0xffff ); - $this->assertSame( $expected, $base->mulInt($value)->limbs,