Last active
July 24, 2022 14:57
-
-
Save Lohann/6c8a3d98a384f937a18362a8fa7d118c to your computer and use it in GitHub Desktop.
Example of Unidirecional Payment Channel in solidty
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity >=0.7.0 <0.9.0; | |
library ECDSA { | |
/** | |
* @notice Recovers the address for an ECDSA signature and message hash, | |
* note that the hash must be prefixed with "\x19Ethereum Signed Message:\n{msg.length}" | |
* @return address The address that was used to sign the message | |
*/ | |
function recoverAddress(bytes32 prefixedHash, bytes memory signature) internal pure returns (address) { | |
(uint8 v, bytes32 r, bytes32 s) = splitSignature(signature); | |
return ecrecover(prefixedHash, v, r, s); | |
} | |
function splitSignature(bytes memory signature) internal pure returns (uint8 v, bytes32 r, bytes32 s) { | |
require(signature.length == 65, "Invalid signature"); | |
assembly { | |
// first 32 bytes, after the length prefix | |
r := mload(add(signature, 32)) | |
// second 32 bytes | |
s := mload(add(signature, 64)) | |
// final byte (first byte of the next 32 bytes) | |
v := byte(0, mload(add(signature, 96))) | |
} | |
} | |
function messageHash(string memory message) internal pure returns (bytes32) { | |
bytes memory prefix = "\x19Ethereum Signed Message:\n"; | |
string memory msgLength = StringUtils.uintToDecString(bytes(message).length); | |
return keccak256(abi.encodePacked(prefix, msgLength, message)); | |
} | |
} | |
library StringUtils { | |
function addressToString(address addr) internal pure returns (string memory) { | |
string memory str = new string(40); | |
assembly { | |
let val := shl(96, addr) | |
let ptr := add(str, 32) | |
let lut := 0x3031323334353637383961626364656600000000000000000000000000000000 | |
let count := 0 | |
for | |
{} | |
lt(count, 20) | |
{count := add(count, 1)} | |
{ | |
let word := byte(count, val) | |
let chrA := div(word, 16) | |
let chrB := mod(word, 16) | |
chrA := byte(chrA, lut) | |
chrB := byte(chrB, lut) | |
let offset := add(count, count) | |
mstore8(add(ptr, offset), chrA) | |
mstore8(add(add(ptr, 1), offset), chrB) | |
} | |
} | |
return str; | |
} | |
function uintToDecString(uint256 n) internal pure returns (string memory) { | |
bytes memory str = new bytes(78); | |
assembly { | |
let ptr := add(str, 32) | |
let num := n | |
for | |
{} | |
gt(num, 0) | |
{} | |
{ | |
let char := add(mod(num, 10), 48) | |
mstore8(ptr, char) | |
num := div(num, 10) | |
ptr := add(ptr, 1) | |
} | |
mstore(str, sub(ptr, add(str, 32))) | |
let start := add(str, 32) | |
let end := sub(ptr, 1) | |
for | |
{} | |
lt(start, end) | |
{} | |
{ | |
let temp := byte(0, mload(end)) | |
mstore8(end, byte(0, mload(start))) | |
mstore8(start, temp) | |
start := add(start, 1) | |
end := sub(end, 1) | |
} | |
} | |
return string(str); | |
} | |
} | |
contract UnidirecionalPaymentChannel { | |
address payable public sender; | |
address payable public recipient; | |
uint256 public expiration; | |
constructor(address payable _recipient, uint256 duration) payable { | |
sender = payable(msg.sender); | |
recipient = _recipient; | |
expiration = block.number + duration; | |
} | |
function getMessage(address to, uint256 amount) public pure returns (string memory) { | |
string memory amountStr = StringUtils.uintToDecString(amount); | |
string memory addrStr = StringUtils.addressToString(to); | |
bytes memory message = abi.encodePacked("Transfer ", amountStr, " WEI to 0x", addrStr); | |
return string(message); | |
} | |
function getMessageHash(address _recipient, uint256 _amount) public pure returns (bytes32) { | |
string memory message = getMessage(_recipient, _amount); | |
return ECDSA.messageHash(message); | |
} | |
/** | |
* Close unidirecional payment channel | |
*/ | |
function close(uint256 amount, bytes memory signature) public { | |
require(msg.sender == recipient); | |
bytes32 messageHash = getMessageHash(recipient, amount); | |
address signer = ECDSA.recoverAddress(messageHash, signature); | |
require(signer == sender); | |
recipient.transfer(amount); // Transfer to recipient | |
selfdestruct(sender); // Reimburse remaining balance and destroy the contract | |
} | |
/** | |
* if the timout is reached without the owner closing the channel, | |
* then the Ether is released back to the recipient | |
*/ | |
function claimTimeout() public { | |
require(block.number >= expiration); | |
selfdestruct(sender); // Reimbuser sender and destroy the contract | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment