Signature

在 PHP 中創建沒有 JSON RPC 節點的簽名消息

  • March 28, 2022

我正在嘗試在 PHP 中重新創建 web3 的 eth.accounts.sign 的效果,我很難找到好的資源如何正確地做到這一點。

我已經看到了這個 (多虧了這個執行緒才找到它)並且根據我的發現,我整理了腳本,它輸出了一些東西,但它絕對是無效的簽名。

<?php

use kornrunner\Keccak;
use Elliptic\EC;

//used libraries simplito/elliptic-php kornrunner/keccak

require_once "vendor/autoload.php";

$ec = new EC('secp256k1');
$pkey = "cdf6a858b71520aedaf50442a761af94a516a3d0e36f2b5b6c9bcf0bfbe45820";
//$addr = "0x703632A0b52244fAbca04aaE138fA8EcaF72dCBC";

$ecPrivateKey = $ec->keyFromPrivate($pkey, 'hex');

$hash = Keccak::hash('Hello World', 256);

$signature = $ecPrivateKey->sign($hash, ['canonical' => true]);

$r = $signature->r->toString(16);
$s = $signature->s->toString(16);
$v = $signature->recoveryParam + 35;

echo "Signed Hello world is:\n";
echo "Using my script:\n";
echo "0x" . $r . $s . $v . "\n";
echo "Using MEW:\n";
echo "0x2f52dfb196b75398b78c0e6c6aee8dc08d7279f2f88af5588ad7728f1e93dd0a479a710365c91ba649deb6c56e2e16836ffc5857cfd1130f159aebd05377d3a01c\n";

這其中有一些令人困惑的部分,我不確定為什麼將 35 添加到 recoveryParam以及為什麼從一開始它總是 0。此外,顯然,鏈 ID 會影響 V,但這種行為在 MEW 簽名中不存在。我不知道哪個順序是正確的 - r s v, or s r v, or v s r,我已經看到它在看似隨機的順序中使用(除了v總是最後一個或第一個),我嘗試在 pkey 之前添加 0x ,此時我沒有想法.

我的問題是我做錯了什麼,或者我在哪裡可以學習如何達到預期的效果(無需閱讀大量我難以理解的 JavaScript 程式碼)。

背景:我需要它來生成將提供給使用者的智能合約的伺服器端參數(即對智能合約其他參數的簽名),並且智能合約必須能夠信任此參數。在伺服器端對其進行簽名將防止潛在的惡意行為者,例如打嗝代理網站並混淆所述參數。

所以方法在很大程度上是正確的,但我有兩部分是錯誤的。首先,v不是+35,而是+27,應該改成十六進制(hexdec())。

更重要的是,乙太坊簽名的消息必須包含類似於“魔術字節”的東西——它是

$message = "\x19Ethereum Signed Message:\n" . strlen($message) . $message;

所以工作和完整的程式碼是:

<?php

use kornrunner\Keccak;
use Elliptic\EC;

require_once "vendor/autoload.php";

$ec = new EC('secp256k1');
$pkey = "0xcdf6a858b71520aedaf50442a761af94a516a3d0e36f2b5b6c9bcf0bfbe45820";
//$addr = "0x703632A0b52244fAbca04aaE138fA8EcaF72dCBC";

$ecPrivateKey = $ec->keyFromPrivate($pkey, 'hex');

$message = "Hello World";

$message = "\x19Ethereum Signed Message:\n" . strlen($message) . $message;

$hash = Keccak::hash($message, 256);

$signature = $ecPrivateKey->sign($hash, ['canonical' => true]);


$r = str_pad($signature->r->toString(16), 64, '0', STR_PAD_LEFT);
$s = str_pad($signature->s->toString(16), 64, '0', STR_PAD_LEFT);
$v = dechex($signature->recoveryParam + 27);

echo "Signed Hello world is:\n";
echo "Using my script:\n";
echo "0x$r$s$v\n";
echo "Using MEW:\n";
echo "0x2f52dfb196b75398b78c0e6c6aee8dc08d7279f2f88af5588ad7728f1e93dd0a479a710365c91ba649deb6c56e2e16836ffc5857cfd1130f159aebd05377d3a01c\n";

現在它可以通過簡單的智能合約進行驗證。

編輯:根據評論中的要求,我包括驗證任意簽名的可靠程式碼。這是我在問這個問題時使用的原始程式碼的縮短版本,因此它使用了一些舊版本的庫和solidity本身。

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/utils/Strings.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/cryptography/ECDSA.sol";

contract SigTest {
   address _owner;

   constructor(address owner) public {
       _owner = owner;
   }

   function IsSigValid (string memory message, bytes memory signature) public view returns(bool) {
       return _owner == ECDSA.recover(
           keccak256(
               abi.encodePacked(
                   "\x19Ethereum Signed Message:\n",
                   Strings.toString(bytes(message).length),
                   message
               )
           ),
           signature
       );
   }
}

接受的答案有點不正確。EC 庫有時(在我的隨機測試中大約為 100 分之一)返回->r->s參數短於 64 個字元。這與 JS 實現不同。

所以你必須對它們進行左零填充:

   $r = str_pad($signature->r->toString(16), 64, '0', STR_PAD_LEFT);
   $s = str_pad($signature->s->toString(16), 64, '0', STR_PAD_LEFT);

顯示這種奇怪行為的數據範例:

$message = '6ef2cd055fde37f0fbd4f33555f667c150765693fafe04744e7eb3e33b42a41c';
$pkey = '0x3b0bf03e36b60dfffe21e894df22af79162549999c864963b5dce06867a32315';

或者

$message = '19a1a0769a2baa13dbc180549039650e66e5735ba749036db447b256305df34c';
$pkey = '0x0a451782bdd735677c2a4e28fcf925a0a8059570485e148f22c7f5b940ad4d19';

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