solidity编写简单支付通道智能合约代码
编写一个简单的支付通道:
Alice是发件人,Bob是收件人,若Alice和Bob之间含有多笔交易,每次都通过以太坊钱包转账,矿工不仅会扣取手续费,且以太坊的出块速度约为10-15s,因此存在交易延迟。
在存在多笔交易时,构建一个简单但完整的支付通道,使用椭圆曲线加密签名以实现安全、即时且无交易费用地重复传输以太币。
该支付通道需要实现发件人在调用合约初期把以太币质押在合约中,根据签名验证判断发件人总共欠收件人多少以太币,在合约有效期内由收件人结束合约并转账给收件人应得以太币,这样整个流程就只需要2次以太坊交易,节省了交易费和时间延迟。
什么是支付通道:
支付通道允许参与者在不发起以太坊交易的情况下重复转移以太币。这意味着你可以节省与交易相关的延迟和费用。我们将探索两方(Alice 和 Bob)之间的简单单向支付通道。它包括三个步骤:
(1)Alice用以太币为智能合约提供资金。这“打开”了支付通道。
(2)Alice签署消息,指定欠Bob多少以太币。每次付款都重复此步骤。
(3)Bob“关闭”支付通道,提取他的部分以太币并将剩余部分发还给Alice。
注意:只有第(1)步和第(3)步需要以太坊交易,第(2)步意味着Alice通过链下方法(如电子邮件)向Bob发送加密签名的消息。这意味着只需要两笔交易即可支持任意数量的转账。
Bob可以保证收到应得资金,因为智能合约托管了Alice的以太币并对消息签名加以验证。此外,为智能合约设计有效期限,即使Bob拒绝关闭支付通道,Alice也可以保证最终收回她的资金。由Alice设置或更改合约有效期。
开通支付通道:
为打开支付通道,Alice需部署智能合约,附加要托管的以太币并指定以太币接收者和合约有效期。这是下面合约代码中函数SimplePaymentChannel的功能。
付款:
Alice通过向Bob发送签名消息来付款,此步骤完全在以太坊网络之外执行。消息由发件人加密签名,然后直接传输给收件人。
每条消息都包含以下信息:
智能合约的地址(用于防止跨合约重放攻击),到目前为止欠收款人的以太币总量。
关闭支付通道:
支付通道仅关闭一次,每条消息都指定了累积的以太币欠款总额,只有最新发送的消息被兑换。当Bob准备好接收以太币时,需通过调用智能合约上的close函数来关闭支付通道,且只有Bob可以调用close函数,他们自然会传递最新的支付消息,因为该消息携带最高的总欠款,该函数向Bob支付应得以太币、向Alice返还剩余以太币,并销毁合约。
支付通道到期:
Bob可以随时关闭支付通道,但如果他们不这样做,Alice需要一种方法来收回她的托管资金。在合约部署时设置了合约有效期。一旦到达有效期,爱丽丝就可以调用函数claimTimeout()来收回资金。调用此函数后,Bob将无法再收到任何以太币,因此Bob需要在到期之前关闭通道。
solidity代码如下:
pragma solidity >=0.7.0 <0.9.0; //用Remix编写,需手动调用function以实现相关功能 contract SimplePaymentChannel{ address payable public sender;//发件人地址 address payable public recipient;//收件人地址 uint public expiration;//存储合约到期时间,防止收件人一直不关闭合约,占用发件人以太币资源 //构造函数,部署合约时调用,仅调用一次 //初始化发件人地址,收件人地址,合约有效时间 constructor(address payable recipientAddress, uint256 duration) payable{ sender = payable(msg.sender);//msg.sender是address类型,需强制类型转换为payable address类型 recipient = recipientAddress; expiration = block.timestamp + duration; } //销毁合约,只有收件人能销毁合约 function close(uint256 amount, bytes memory signature) external{ //require()中判断条件为true则继续,为false则退出该function,回退该function内所有更改 require(msg.sender == recipient);//判断调用该function地址是否为收件人 require(isValidSignature(amount, signature));//判断收件人是否掌握有正确的的发件人消息签名 recipient.transfer(amount);//把应得的以太币发送给收件人,谁调用transfer(),就给谁转账 selfdestruct(sender);//销毁当前合约,将合约剩余资金发送到给定地址sender //由于合约内容已被记录在旧的区块上,仍可以被查询,但不能被再次调用,除非重新部署该合约 } //合约有效期续期,仅有发件人可以调用 function extend(uint256 newExpiration) external{ require(msg.sender == sender);//判断调用者是否为发件人 require(newExpiration > expiration);//判断新的有效期是否大于当前有效期 expiration = newExpiration;//重置合约有效期 } //判断当前合约是否在有效期内 function claimTimeout() external{ require(block.timestamp >= expiration);//判断当前合约是否过期,若过期,则销毁合约 selfdestruct(sender);//销毁合约 } //函数isValidSignature(),splitSignature(),recoverSigner(),prefixed()涉及到 椭圆曲线加密 消息的验证过程, //详见我的另一篇博客https://www.cnblogs.com/forkroad/p/16121333.html,有详细介绍 function isValidSignature(uint256 amount, bytes memory signature) internal view returns(bool){ bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));//根据当前地址this和转账金额amount双重加密为消息message return recoverSigner(message, signature) == sender;// } function splitSignature(bytes memory sig) internal pure returns(uint8 v, bytes32 r, bytes32 s){ require(sig.length == 65); assembly{ r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } return (v, r, s); } function recoverSigner(bytes32 message, bytes memory sig) internal pure returns(address){ (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); return ecrecover(message, v, r, s); } function prefixed(bytes32 hash) internal pure returns(bytes32){ return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); } }
来源(solidity官方英文文档0.8.13):https://docs.soliditylang.org/en/v0.8.13/solidity-by-example.html#writing-a-simple-payment-channel
solidity官方中文文档0.8.0:https://learnblockchain.cn/docs/solidity/solidity-by-example.html#id15
椭圆曲线签名验证代码实现:https://www.cnblogs.com/forkroad/p/16121333.html