随着Web3和去中心化应用的兴起,用户与区块链的交互变得越来越普遍,Web3钱包(如MetaMask、Trust Wallet等)作为用户管理私钥、与区块链应用交互的关键入口,其集成也成为许多后端开发者面临的课题,本文将详细介绍如何使用PHP语言连接Web3钱包,实现用户身份验证、签名以及与智能合约的交互等核心功能。
为什么PHP开发者需要连接Web3钱包
PHP作为一种广泛使用的服务器端脚本语言,拥有庞大的开发者社区和成熟的生态系统,虽然Web3生态中JavaScript(特别是Node.js和前端框架)更为流行,但许多现有系统或后端服务是基于PHP构建的,将Web3钱包集成到PHP后端,可以实现:
- 用户身份验证:通过钱包签名进行去中心化身份认证(DID),替代传统的用户名密码登录。
- 交易签名与广播:后端可以发起交易请求,由用户在前端钱包中签名确认后,再由后端或服务节点广播到区块链。
- 数据交互:读取钱包地址信息、代币余额,或与智能合约进行读写交互。
- 扩展现有系统:为传统的PHP应用(如电商、社交平台)引入Web3功能,如NFT交易、DAO治理等。
准备工作:环境与工具
在开始之前,你需要准备以下环境和工具:
- PHP环境:确保你的PHP版本较新(建议7.4+),并安装了必要的扩展。
curl:用于发送HTTP请求。json:PHP内置,用于处理JSON数据。openssl:PHP内置,用于处理加密和签名(部分库可能需要)。
- Web3钱包库:PHP本身不内置Web3功能,我们需要借助第三方库,目前比较流行且维护较好的有:
- web3.php (https://github.com/sc0vu/web3.php):一个功能相对全面的Web3 PHP库,支持以太坊坊、ENS、合约交互等。
- php-ethereum (https://github.com/digitaldonkey/php-ethereum):另一个选择,但功能可能不如web3.php丰富。
本文将以
web3.php为例进行讲解。
- 以太坊节点或RPC服务:你的PHP应用需要一个与以太坊网络(或其他兼容网络)通信的入口,你可以选择:
- 运行自己的节点:如使用Geth或OpenEthereum,资源消耗较大。
- 使用第三方RPC服务:如Infura、Alchemy、QuickNode等,提供稳定可靠的RPC接口,通常有免费套餐,这是初学者和大多数开发者的首选。
安装Web3.php库
最简单的方式是通过Composer来安装web3.php库:
composer require sc0vu/web3.php
安装完成后,你可以在PHP项目中引入自动加载文件:
require 'vendor/autoload.php';
连接到Web3钱包(通过RPC节点)
连接Web3钱包的本质是通过RPC节点与区块链网络通信。web3.php库封装了这些底层细节。
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
// 替换为你的RPC节点URL,例如Infura或Alchemy提供的URL
$rpcUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
// 创建HTTP Provider
$provider = new HttpProvider(new HttpRequestManager($rpcUrl, 5000)); // 5000是超时时间(毫秒)
// 创建Web3实例
$web3 = new Web3($provider);
// 测试连接
$web3->getVersion()->then(function ($version) {
echo "Ethereum Client Version: " . $version . PHP_EOL;
})->catch(function ($error) {
echo "Error: " . $error->getMessage() . PHP_EOL;
});
如果成功连接,你将看到以太坊客户端的版本号(如Geth/v1.10.23/linux-amd64/go1.17.13)。
连接Web3钱包的核心:用户签名认证
直接“连接”钱包并获取用户私钥在PHP后端是不安全且不推荐的,因为私钥一旦泄露,钱包资产将面临风险,常见的做法是前端钱包签名,后端验证。
前端发起签名请求(示例,使用 ethers.js 或 web3.js)
用户在前端点击“连接钱包”或“登录”按钮后,前端会请求用户钱包对一段特定消息进行签名。
// 前端伪代码 (使用 ethers.js)
async function signInWithWeb3() {
if (!window.ethereum) {
alert('Please install MetaMask!');
return;
}
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []); // 请求用户授权
const signer = provider.getSigner();
const address = await signer.getAddress();
// 定义要签名的消息
const message = `Welcome to DApp! Please sign this message to authenticate: ${Date.now()}`;
// 请求用户签名
try {
const signature = await signer.signMessage(message);
// 将 address 和 signature 发送到后端进行验证
const response = await fetch('/api/authenticate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address, message, signature })
});
const result = await response.json();
console.log('Authentication result:', result);
} catch (error) {
console.error('Error signing message:', error);
}
}
后端验证签名(PHP + web3.php)
后端接收到前端传来的address、message和signature后,需要验证签名是否确实由该地址对应私钥签发。
use Web3\Utils; use Web3\Personal; // 注意:Personal模块在某些节点可能被禁用,推荐使用ecrecover // 假设从前端接收到的数据 $addressFromFrontend = '0xUserAddressFromFrontend'; $messageFromFrontend = 'Welcome to DApp! Please sign this message to authenticate: 1234567890'; $signatureFromFrontend = '0xSignatureFromFrontend'; // 方法一:使用web3.php的ecrecover (推荐) try { $recoveredAddress = $web3->getEth()->ecrecover( $messageFromFrontend, $signatureFromFrontend )->wait(); // $recoveredAddress 是一个包含地址的对象,需要格式化 $recoveredAddress = $recoveredAddress->toString(); if (strtolower($recoveredAddress) === strtolower($addressFromFrontend)) { echo "Signature verified! Address: " . $recoveredAddress . PHP_EOL; // 在这里可以生成应用的JWT token或进行其他登录逻辑 } else { echo "Signature verification failed! Recovered address: " . $recoveredAddress . PHP_EOL; } } catch (\Exception $e) { echo "Error verifying signature: " . $e->getMessage() . PHP_EOL; } // 方法二:如果Personal模块可用(不推荐,因为节点可能禁用) // $personal = new Personal($web3->getProvider()); // $personal->ecrecover( // $messageFromFrontend, // $signatureFromFrontend, // function ($err, $address) use ($addressFromFrontend) { // if ($err !== null) { // echo "Error: " . $err->getMessage() . PHP_EOL; // return; // } // if (strtolower($address) === strtolower($addressFromFrontend)) { // echo "Signature verified! Address: " . $address . PHP_EOL; // } else { // echo "Signature verification failed!" . PHP_EOL; // } // } // );
ecrecover是以太坊的一个预编译合约,可以根据消息、签名和恢复ID(如果需要,从签名中提取)恢复出签名者的地址,如果恢复的地址与前端传来的地址一致,则签名有效。
与智能合约交互
连接钱包的另一个重要目的是与智能合约交互。web3.php提供了合约交互的功能。
你需要合约的ABI(Application Binary Interface)和地址。
use Web3\Contracts\Ethabi;
use Web3\Contracts\Contract;
// 合约ABI (JSON格式)
$abi = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}, ...]';
// 合约地址
$contractAddress = '0xYourContractAddress';
// 创建合约实例
$contract = new Contract($web3->getProvider(), $abi);
// 调用合约常量/函数 (read)
$contract->at($contractAddress)->call('name',
