在 PHP 中創建沒有 JSON RPC 節點的簽名消息
我正在嘗試在 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
, ors r v
, orv 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';