Signature

為什麼我對已簽名消息簽名的驗證在 PHP 中不起作用?

  • April 16, 2022

我正在嘗試驗證 PHP 中的簽名消息。

需要明確的是,我不希望與 JSON-RPC 或任何外部服務進行互動,我既 a) 知道這些服務有效,並且 b) 成功驗證了我提供的範例簽名消息。

此外,我很清楚“消息前綴”和消息長度問題。那不是我遇到問題的地方。

問題似乎源於我正在使用的 Signature 類(包括在下面的內聯,為了便於閱讀,刪除了所有未使用的位,但從這裡提取:https ://github.com/tuaris/CryptoCurrencyPHP/blob/ master/Signature.class.php)或從簽名本身提取的 R 和 S 值的 GMP 轉換。

我的程式碼中使用的唯一外部庫是https://github.com/0xbb/php-sha3/blob/master/src/Sha3.php,它需要修改第 334 行,更改0x060x01,以實現 keccak 兼容性,這就是乙太坊使用的。(我知道這對我來說是一個正確的更改,因為0x01在使用 web3 庫對同一消息進行散列時,將其保持為對原始消息進行散列時會產生不同的結果。

下面是我的程式碼。如果有人能夠告訴我哪裡出錯了,我將不勝感激。

<?php
use bb\Sha3\Sha3;
require_once('./Sha3.php');

$message = 'This is an example of a signed message.';
$signerAddress = '0xd4e01f608982ff53022e8c3ff43e145a192a9c4a';
$signedMessage = '0x6a65ed07a44715169177223ce508a2257f8167db452df0b2e37966b39350a61940e370616b3a0ea0f20adfa4661a7db10eeb583ca5a58ec8468e726eff4131a11c';
$signedMessageStrip = '6a65ed07a44715169177223ce508a2257f8167db452df0b2e37966b39350a61940e370616b3a0ea0f20adfa4661a7db10eeb583ca5a58ec8468e726eff4131a11c';

$prefix = "\x19Ethereum Signed Message:\n".strlen($message);
$stringToSign = $prefix.$message;
//\x19Ethereum Signed Message:\n39This is an example of a signed message.

$messageHex = Sha3::hash($stringToSign, 256); //this matches web3.sha() output for the given message and prefix.
$messageGmp = gmp_init("0x".$messageHex);

$r = substr($signedMessageStrip, 0,64);
$s = substr($signedMessageStrip, 64,64);
$v = substr($signedMessageStrip, 128,2);

$vChecksum = hexdec($v) - 27;
if($vChecksum !== 0 && $vChecksum !== 1) { echo "Invalid checksum.\n"; exit; }

$rGmp = gmp_init("0x".$r);
$sGmp = gmp_init("0x".$s);

$publicKey = Signature::recoverPublicKey($rGmp, $sGmp, $messageGmp, $vChecksum);

//the below line is where things are going wrong. The output hash of Sha3::hash($publicKey['x'].$publicKey['y'], 256) is not correct, according to stepping through similar processes using the web3 library, which generates different results, despite an earlier check that publicKey *is* correct. I cannot figure out what's going wrong.
$recovered = "0x".substr(Sha3::hash($publicKey['x'].$publicKey['y'], 256),24)."\n"; //convert to public address format
//$recovered = 0xf2517bd73c56d6d5a5409c4a1ee29c8f2d5438ff

if (strtolower($recovered) == strtolower($signerAddress)) { echo "Address recovered successfully.\n"; }
else { echo "Address NOT recovered successfully.\n"; }

?>
<?php
class SECp256k1 {
   public $a;
   public $b;
   public $p;
   public $n;
   public $G;
   public function __construct(){
       $this->a = gmp_init('0', 10);
       $this->b = gmp_init('7', 10);
       $this->p = gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16);
       $this->n = gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16);
       $this->G = array('x' => gmp_init('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
                        'y' => gmp_init('32670510020758816978083085130507043184471273380659243275938904335757337482424'));
   }
}

class Signature {
   public static function recoverPublicKey($R, $S, $hash, $recoveryFlags){
       $secp256k1 = new SECp256k1();
       $a = $secp256k1->a;
       $b = $secp256k1->b;
       $G = $secp256k1->G;
       $n = $secp256k1->n;
       $p = $secp256k1->p;
       $isYEven = ($recoveryFlags & 1) != 0;
       $isSecondKey = ($recoveryFlags & 2) != 0;
       // PointMathGMP::mulPoint wants HEX String
       $e = gmp_strval($hash, 16);
       $s = gmp_strval($S, 16);
       // Precalculate (p + 1) / 4 where p is the field order
       // $p_over_four is GMP
       static $p_over_four; // XXX just assuming only one curve/prime will be used
       if (!$p_over_four) {
           $p_over_four = gmp_div(gmp_add($p, 1), 4);
       }
       // 1.1 Compute x
       // $x is GMP
       if (!$isSecondKey) {
           $x = $R;
       } else {
           $x = gmp_add($R, $n);
       }
       // 1.3 Convert x to point
       // $alpha is GMP
       $alpha = gmp_mod(gmp_add(gmp_add(gmp_pow($x, 3), gmp_mul($a, $x)), $b), $p);
       // $beta is DEC String (INT)
       $beta = gmp_strval(gmp_powm($alpha, $p_over_four, $p));
       // If beta is even, but y isn't or vice versa, then convert it,
       // otherwise we're done and y == beta.
       if (PointMathGMP::isEvenNumber($beta) == $isYEven) {
           // gmp_sub function will convert the DEC String "$beta" into a GMP
           // $y is a GMP 
           $y = gmp_sub($p, $beta);
       } else {
           // $y is a GMP
           $y = gmp_init($beta);
       }
       // 1.4 Check that nR is at infinity (implicitly done in construtor) -- Not reallly
       // $Rpt is Array(GMP, GMP)
       $Rpt = array('x' => $x, 'y' => $y);
       // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
       // $rInv is a HEX String
       $rInv = gmp_strval(gmp_invert($R, $n), 16);
       // $eGNeg is Array (GMP, GMP)
       $eGNeg = PointMathGMP::negatePoint(PointMathGMP::mulPoint($e, $G, $a, $b, $p));
       $sR = PointMathGMP::mulPoint($s, $Rpt, $a, $b, $p);
       $sR_plus_eGNeg = PointMathGMP::addPoints($sR, $eGNeg, $a, $p);
       // $Q is Array (GMP, GMP)
       $Q = PointMathGMP::mulPoint($rInv, $sR_plus_eGNeg, $a, $b, $p);
       // Q is the derrived public key
       // $pubkey is Array (HEX String, HEX String)
       // Ensure it's always 64 HEX Charaters
       $pubKey['x'] = str_pad(gmp_strval($Q['x'], 16), 64, 0, STR_PAD_LEFT);
       $pubKey['y'] = str_pad(gmp_strval($Q['y'], 16), 64, 0, STR_PAD_LEFT);
       return $pubKey;
   }
}
class PointMathGMP {
   /***
    * Computes the result of a point addition and returns the resulting point as an Array.
    *
    * @param Array $pt
    * @return Array Point
    * @throws \Exception
    */
   public static function doublePoint(Array $pt, $a, $p)
   {
       $gcd = gmp_strval(gmp_gcd(gmp_mod(gmp_mul(gmp_init(2, 10), $pt['y']), $p),$p));
       if($gcd != '1')
       {
           throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
       }
       // SLOPE = (3 * ptX^2 + a )/( 2*ptY )
       // Equals (3 * ptX^2 + a ) * ( 2*ptY )^-1
       $slope = gmp_mod(
                        gmp_mul(
                                gmp_invert(
                                           gmp_mod(
                                                   gmp_mul(
                                                           gmp_init(2, 10),
                                                           $pt['y']
                                                   ),
                                                   $p
                                           ),
                                           $p
                                ),
                                gmp_add(
                                        gmp_mul(
                                                gmp_init(3, 10),
                                                gmp_pow($pt['x'], 2)
                                        ),
                                        $a
                                )
                        ),
                        $p
               );
       // nPtX = slope^2 - 2 * ptX
       // Equals slope^2 - ptX - ptX
       $nPt = array();
       $nPt['x'] = gmp_mod(
                           gmp_sub(
                                   gmp_sub(
                                           gmp_pow($slope, 2),
                                           $pt['x']
                                   ),
                                   $pt['x']
                           ),
                           $p
                   );
       // nPtY = slope * (ptX - nPtx) - ptY
       $nPt['y'] = gmp_mod(
                           gmp_sub(
                                   gmp_mul(
                                           $slope,
                                           gmp_sub(
                                                   $pt['x'],
                                                   $nPt['x']
                                           )
                                   ),
                                   $pt['y']
                           ),
                           $p
                   );
       return $nPt;
   }
   /***
    * Computes the result of a point addition and returns the resulting point as an Array.
    *
    * @param Array $pt1
    * @param Array $pt2
    * @return Array Point
    * @throws \Exception
    */
   public static function addPoints(Array $pt1, Array $pt2, $a, $p)
   {
       if(gmp_cmp($pt1['x'], $pt2['x']) == 0  && gmp_cmp($pt1['y'], $pt2['y']) == 0) //if identical
       {
           return self::doublePoint($pt1, $a, $p);
       }
       $gcd = gmp_strval(gmp_gcd(gmp_sub($pt1['x'], $pt2['x']), $p));
       if($gcd != '1')
       {
           throw new \Exception('This library doesn\'t yet support points at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
       }
       // SLOPE = (pt1Y - pt2Y)/( pt1X - pt2X )
       // Equals (pt1Y - pt2Y) * ( pt1X - pt2X )^-1
       $slope      = gmp_mod(
                             gmp_mul(
                                     gmp_sub(
                                             $pt1['y'],
                                             $pt2['y']
                                     ),
                                     gmp_invert(
                                                gmp_sub(
                                                        $pt1['x'],
                                                        $pt2['x']
                                                ),
                                                $p
                                     )
                             ),
                             $p
                     );
       // nPtX = slope^2 - ptX1 - ptX2
       $nPt = array();
       $nPt['x']   = gmp_mod(
                             gmp_sub(
                                     gmp_sub(
                                             gmp_pow($slope, 2),
                                             $pt1['x']
                                     ),
                                     $pt2['x']
                             ),
                             $p
                     );
       // nPtX = slope * (ptX1 - nPtX) - ptY1
       $nPt['y']   = gmp_mod(
                             gmp_sub(
                                     gmp_mul(
                                             $slope,
                                             gmp_sub(
                                                     $pt1['x'],
                                                     $nPt['x']
                                             )
                                     ),
                                     $pt1['y']
                             ),
                             $p
                     );
       return $nPt;
   }
   /***
    * Computes the result of a point multiplication and returns the resulting point as an Array.
    *
    * @param String Hex $k
    * @param Array $pG (GMP, GMP)
    * @param $base (INT)
    * @throws \Exception
    * @return Array Point (GMP, GMP)
    */
   public static function mulPoint($k, Array $pG, $a, $b, $p, $base = null)
   {
       //in order to calculate k*G
       if($base == 16 || $base == null || is_resource($base))
           $k = gmp_init($k, 16);
       if($base == 10)
           $k = gmp_init($k, 10);
       $kBin = gmp_strval($k, 2);
       $lastPoint = $pG;
       for($i = 1; $i < strlen($kBin); $i++)
       {
           if(substr($kBin, $i, 1) == 1 )
           {
               $dPt = self::doublePoint($lastPoint, $a, $p);
               $lastPoint = self::addPoints($dPt, $pG, $a, $p);
           }
           else
           {
               $lastPoint = self::doublePoint($lastPoint, $a, $p);
           }
       }
       if(!self::validatePoint(gmp_strval($lastPoint['x'], 16), gmp_strval($lastPoint['y'], 16), $a, $b, $p)){
           throw new \Exception('The resulting point is not on the curve.');
       }
       return $lastPoint;
   }
   /***
    * Returns true if the point is on the curve and false if it isn't.
    *
    * @param $x
    * @param $y
    * @return bool
    */
   public static function validatePoint($x, $y, $a, $b, $p)
   {
       $x  = gmp_init($x, 16);
       $y2 = gmp_mod(
                       gmp_add(
                           gmp_add(
                               gmp_powm($x, gmp_init(3, 10), $p),
                               gmp_mul($a, $x)
                           ),
                           $b
                       ),
                       $p
                   );
       $y = gmp_mod(gmp_pow(gmp_init($y, 16), 2), $p);
       if(gmp_cmp($y2, $y) == 0)
           return true;
       else
           return false;
   }
   /***
    * Returns Negated Point (Y).
    *
    * @param $point Array(GMP, GMP)
    * @return Array(GMP, GMP)
    */
   public static function negatePoint($point) { 
       return array('x' => $point['x'], 'y' => gmp_neg($point['y'])); 
   }
   // Checks is the given number (DEC String) is even
   public static function isEvenNumber($number) {
       return (((int)$number[strlen($number)-1]) & 1) == 0;
   }
}
?>

經過多次頭部撞擊(撞牆)並將問題隔離到生成的公鑰的散列之後,(很好地總結在我的另一個問題中,在這裡:為什麼我的私鑰的公鑰沒有生成正確的公共地址?),事實證明,必須將公鑰的字節而不是十六進制散列本身傳遞給 keccak 散列算法。

$recovered = "0x".substr(Sha3::hash(hex2bin($publicKey['x'].$publicKey['y']), 256),24)

經過同樣長時間的敲打,我在 PHP https://github.com/digitaldonkey/ecverify中為 web3.ecverify 編寫了一個 PHP 等效項

引用自:https://ethereum.stackexchange.com/questions/39945