Skip to content

Instantly share code, notes, and snippets.

@Lohann
Last active July 24, 2022 14:57
Show Gist options
  • Save Lohann/6c8a3d98a384f937a18362a8fa7d118c to your computer and use it in GitHub Desktop.
Save Lohann/6c8a3d98a384f937a18362a8fa7d118c to your computer and use it in GitHub Desktop.
Example of Unidirecional Payment Channel in solidty
// 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