为什么我对签名消息的签名验证在PHP代码中未工作?

简介: 我正在尝试验证PHP中的签名消息。我不希望与JSON-RPC或任何外部服务有接口,我知道这两个服务都是可以成功地验证了我提供的示例签名消息。

我正在尝试验证PHP中的签名消息。

我不希望与JSON-RPC或任何外部服务有接口,我知道这两个服务都是可以成功地验证了我提供的示例签名消息。

另外,我很清楚message prefix和消息长度问题。

这个问题似乎源于我正在使用的Signature类或对R和S值的GMP转换,在签名本身中起的作用。

在我的代码中使用的唯一外部库是https://github.com/0xbb/php-sha3/blob/master/src/Sha3.php,它需要对第334行进行修改,将0x06更改为0x01,用于keccak兼容,这是以太坊所使用的。这里的改变应该说是一个正确的改变,因为在用web3库hash同一条消息时,当哈希原始消息时,将其保持为0x01会产生不同的结果。

下面是我的代码。如果有人能告诉我哪里出了问题,我将不胜感激。

<?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;
    }
}
?>

问题解答

将问题与所产生的公钥的hash分开,事实证明,必须将公钥的字节,而不是十六进制hash本身传递给keccak哈希算法。

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

原文《以太坊常见问题和错误》中的:
http://cw.hubwiz.com/card/c/ethereum-FAQ/1/1/20/

另外推荐几个很受欢迎全网稀缺的互动教程:

  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。
  • python以太坊,主要是针对python围绕web3.py进行区块链以太坊应用开发的讲解。
  • web3j,主要是针对java和android程序员围绕web3j库进行区块链以太坊开发的讲解。
  • 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
相关文章
|
9天前
|
安全 程序员 PHP
PHP中的异常处理:提升代码的健壮性
【10月更文挑战第8天】在编程的世界中,错误和异常是不可避免的。它们就像路上的坑洼,可能会让我们的程序“跌倒”。但是,如果我们能够正确地处理这些异常,就可以让我们的程序更加稳健,就像我们学会了如何在坑洼的路上稳稳地行走一样。本文将介绍PHP中的异常处理机制,以及如何使用它来提升我们的代码质量。
|
28天前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
1月前
|
存储 Java 数据库连接
php学习笔记-代码基本语法-day01
本文是关于PHP编程语言的基础语法学习笔记,内容包括PHP的介绍、注释风格、数据类型、命名规范、常量和变量的使用,以及变量和常量相关的常用函数。文中详细解释了PHP的基本语法元素和一些易混淆的概念,如传值赋值与传址赋值、可变变量,以及如何检查变量是否已定义或为空。
php学习笔记-代码基本语法-day01
|
4天前
|
设计模式 算法 数据库连接
PHP中的设计模式:提高代码的可维护性和扩展性
【10月更文挑战第13天】 本文将探讨PHP中常见的设计模式及其在实际项目中的应用。通过对比传统编程方式,我们将展示设计模式如何有效地提高代码的可维护性和扩展性。无论是单例模式确保类的单一实例,还是观察者模式实现对象间的松耦合,每一种设计模式都为开发者提供了解决特定问题的最佳实践。阅读本文后,读者将能更好地理解和应用这些设计模式,从而提升PHP编程的效率和质量。
|
18天前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP开发领域,设计模式是解决常见问题的高效方案集合。它们不是具体的代码,而是一种编码和设计经验的总结。单例模式作为设计模式中的一种,确保了一个类仅有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的基本概念、实现方式及其在PHP中的应用。
单例模式在PHP中的应用广泛,尤其在处理数据库连接、日志记录等场景时,能显著提高资源利用率和执行效率。本文从单例模式的定义出发,详细解释了其在PHP中的不同实现方法,并探讨了使用单例模式的优势与注意事项。通过对示例代码的分析,读者将能够理解如何在PHP项目中有效应用单例模式。
|
26天前
|
程序员 数据库连接 PHP
PHP中的异常处理:提升代码的健壮性
【9月更文挑战第30天】在编程的世界里,错误和异常是不可避免的。本文将深入探索PHP中异常处理的机制,揭示如何通过有效的异常管理来增强代码的健壮性和可靠性。我们将从基础概念出发,逐步深入到高级应用,最后通过实际代码示例来巩固理论知识。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的指导和启示。
34 6
|
27天前
|
设计模式 算法 PHP
PHP中的设计模式:提升代码的灵活性与可维护性
在本文中,我们将深入探讨PHP编程语言中的一种重要概念——设计模式。设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它代表了最佳的实践,被有经验的面向对象的软件开发人员所采用。本文将通过具体的实例,展示如何在PHP项目中应用设计模式,以提高代码的灵活性和可维护性。无论你是PHP初学者还是经验丰富的开发者,都能从中获得有价值的见解。
|
29天前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入探索与实践在软件开发的广袤天地中,PHP以其独特的魅力和强大的功能,成为无数开发者手中的得力工具。而在这条充满挑战与机遇的征途上,设计模式犹如一盏明灯,指引着我们穿越代码的迷雾,编写出更加高效、灵活且易于维护的程序。今天,就让我们聚焦于设计模式中的璀璨明珠——策略模式,深入探讨其在PHP中的实现方法及其实际应用价值。
策略模式,这一设计模式的核心在于它为软件设计带来了一种全新的视角和方法。它允许我们在运行时根据不同情况选择最适合的解决方案,从而极大地提高了程序的灵活性和可扩展性。在PHP这门广泛应用的编程语言中,策略模式同样大放异彩,为开发者们提供了丰富的创作空间。本文将从策略模式的基本概念入手,逐步深入到PHP中的实现细节,并通过一个具体的实例来展示其在实际项目中的应用效果。我们还将探讨策略模式的优势以及在实际应用中可能遇到的挑战和解决方案,为PHP开发者提供一份宝贵的参考。
|
28天前
|
设计模式 存储 数据库连接
探索PHP中的设计模式:提高代码的可维护性与扩展性
本文将深入探讨PHP中常用的设计模式,包括单例模式、工厂模式和观察者模式。通过具体的代码示例,展示如何在实际项目中应用这些设计模式,以提高代码的可维护性与扩展性。无论你是PHP初学者还是有一定经验的开发者,都可以通过本文的学习,提升你的编程技巧和项目架构能力。
|
9天前
|
PHP 开发者 UED
PHP中的异常处理:提升代码的健壮性
【10月更文挑战第8天】在编程世界中,错误和异常是不可避免的。它们如同路上的绊脚石,让我们的程序跌倒。但如果我们学会了如何处理这些异常,就能像学会走路的孩子一样,即使跌倒也能爬起来继续前进。本文将引导你如何在PHP中捕获并处理异常,让你的代码更加健壮,能够优雅地面对各种挑战。