欢迎订阅专栏 :3分钟Solidity--智能合约--Web3区块链技术必学
如需获取本内容的最新版本,请参见 Cyfrin.io 上的单向支付通道(代码示例)
支付通道允许参与者在链下反复转移以太币。
该合约的使用方式如下:
Alice部署合约,并存入一定数量的以太币。Alice通过离线签署消息授权支付,并将签名发送给Bob。Bob向智能合约出示签名消息以申领付款。- 如果
Bob未申领付款,合约到期后Alice可收回她的以太币。
这种机制被称为单向支付通道,因为资金只能从Alice单向流向Bob。
scss
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "./ECDSA.sol";
contract ReentrancyGuard {
bool private locked;
modifier guard() {
require(!locked);
locked = true;
_;
locked = false;
}
}
contract UniDirectionalPaymentChannel is ReentrancyGuard {
using ECDSA for bytes32;
address payable public sender;
address payable public receiver;
uint256 private constant DURATION = 7 * 24 * 60 * 60;
uint256 public expiresAt;
constructor(address payable _receiver) payable {
require(_receiver != address(0), "receiver = zero address");
sender = payable(msg.sender);
receiver = _receiver;
expiresAt = block.timestamp + DURATION;
}
function _getHash(uint256 _amount) private view returns (bytes32) {
// NOTE: 签署包含本合约地址的签名以防止对其他合约的重放攻击
return keccak256(abi.encodePacked(address(this), _amount));
}
function getHash(uint256 _amount) external view returns (bytes32) {
return _getHash(_amount);
}
function _getEthSignedHash(uint256 _amount)
private
view
returns (bytes32)
{
return _getHash(_amount).toEthSignedMessageHash();
}
function getEthSignedHash(uint256 _amount)
external
view
returns (bytes32)
{
return _getEthSignedHash(_amount);
}
function _verify(uint256 _amount, bytes memory _sig)
private
view
returns (bool)
{
return _getEthSignedHash(_amount).recover(_sig) == sender;
}
function verify(uint256 _amount, bytes memory _sig)
external
view
returns (bool)
{
return _verify(_amount, _sig);
}
function close(uint256 _amount, bytes memory _sig) external guard {
require(msg.sender == receiver, "!receiver");
require(_verify(_amount, _sig), "invalid sig");
(bool sent,) = receiver.call{value: _amount}("");
require(sent, "Failed to send Ether");
selfdestruct(sender);
}
function cancel() external {
require(msg.sender == sender, "!sender");
require(block.timestamp >= expiresAt, "!expired");
selfdestruct(sender);
}
}
scss
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
} else if (error == RecoverError.InvalidSignatureV) {
revert("ECDSA: invalid signature 'v' value");
}
}
function tryRecover(bytes32 hash, bytes memory signature)
internal
pure
returns (address, RecoverError)
{
// 检查签名长度
// - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover 接收签名参数,目前唯一获取这些参数的方式是通过汇编代码。
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover函数需要签名参数,目前获取这些参数的唯一方法是使用汇编代码。
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
}
return tryRecover(hash, r, vs);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
function recover(bytes32 hash, bytes memory signature)
internal
pure
returns (address)
{
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs)
internal
pure
returns (address, RecoverError)
{
bytes32 s = vs
& bytes32(
0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
function recover(bytes32 hash, bytes32 r, bytes32 vs)
internal
pure
returns (address)
{
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (address, RecoverError)
{
// EIP-2仍然允许ecrecover()存在签名可塑性。
// 消除这种可能性,使签名具有唯一性。
// 以太坊黄皮书(<https://ethereum.github.io/yellowpaper/paper.pdf>)附录F
// 定义了s的有效范围(第301条):0 < s < secp256k1n ÷ 2 + 1;
// 以及v的有效范围(第302条):v ∈ {27, 28}。
// 当前大多数库生成的签名都具有s值位于低半序区间的唯一性特征。
//
// 如果你的库生成了可变的签名,例如s值在较高范围内,可以计算一个新的s值
// 使用0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141减去s1,并将v从27翻转为28或
// 反之亦然。如果你的库还生成了v值为0/1而非27/28的签名,则将v加27以同样接受
// 这些可变签名。
if (
uint256(s)
> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
) {
return (address(0), RecoverError.InvalidSignatureS);
}
if (v != 27 && v != 28) {
return (address(0), RecoverError.InvalidSignatureV);
}
// 如果签名有效(且不可篡改),则返回签名者地址
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (address)
{
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
function toEthSignedMessageHash(bytes32 hash)
internal
pure
returns (bytes32)
{
// 32是哈希值的字节长度
// 由上述类型签名强制执行
return keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
}
}
Remix Lite 尝试一下