-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBiDirectionalPaymentChannel.sol
More file actions
165 lines (134 loc) · 4.82 KB
/
BiDirectionalPaymentChannel.sol
File metadata and controls
165 lines (134 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
pragma experimental ABIEncoderV2;
/*
Opening a channel
1. Alice and Bob fund a multi-sig wallet
2. Precompute payment channel address
3. Alice and Bob exchanges signatures of initial balances
4. Alice and Bob creates a transaction that can deploy a payment channel from
the multi-sig wallet
Update channel balances
1. Repeat steps 1 - 3 from opening a channel
2. From multi-sig wallet create a transaction that will
- delete the transaction that would have deployed the old payment channel
- and then create a transaction that can deploy a payment channel with the
new balances
Closing a channel when Alice and Bob agree on the final balance
1. From multi-sig wallet create a transaction that will
- send payments to Alice and Bob
- and then delete the transaction that would have created the payment channel
Closing a channel when Alice and Bob do not agree on the final balances
1. Deploy payment channel from multi-sig
2. call challengeExit() to start the process of closing a channel
3. Alice and Bob can withdraw funds once the channel is expired
*/
import "./openzeppelin/SafeMath.sol";
import "./openzeppelin/ECDSA.sol";
contract BiDirectionalPaymentChannel {
using SafeMath for uint;
using ECDSA for bytes32;
event ChallengeExit(address indexed sender, uint nonce);
event Withdraw(address indexed to, uint amount);
address payable[2] public users;
mapping(address => bool) public isUser;
mapping(address => uint) public balances;
uint public challengePeriod;
uint public expiresAt;
uint public nonce;
modifier checkBalances(uint[2] memory _balances) {
require(
address(this).balance >= _balances[0].add(_balances[1]),
"balance of contract must be >= to the total balance of users"
);
_;
}
// NOTE: deposit from multi-sig wallet
constructor(
address payable[2] memory _users,
uint[2] memory _balances,
uint _expiresAt,
uint _challengePeriod
) payable checkBalances(_balances) {
require(_expiresAt > block.timestamp, "Expiration must be > now");
require(_challengePeriod > 0, "Challenge period must be > 0");
for (uint i = 0; i < _users.length; i++) {
address payable user = _users[i];
require(!isUser[user], "user must be unique");
users[i] = user;
isUser[user] = true;
balances[user] = _balances[i];
}
expiresAt = _expiresAt;
challengePeriod = _challengePeriod;
}
function verify(
bytes[2] memory _signatures,
address _contract,
address[2] memory _signers,
uint[2] memory _balances,
uint _nonce
) public pure returns (bool) {
for (uint i = 0; i < _signatures.length; i++) {
/*
NOTE: sign with address of this contract to protect
agains replay attack on other contracts
*/
bool valid = _signers[i] ==
keccak256(abi.encodePacked(_contract, _balances, _nonce))
.toEthSignedMessageHash()
.recover(_signatures[i]);
if (!valid) {
return false;
}
}
return true;
}
modifier checkSignatures(
bytes[2] memory _signatures,
uint[2] memory _balances,
uint _nonce
) {
// Note: copy storage array to memory
address[2] memory signers;
for (uint i = 0; i < users.length; i++) {
signers[i] = users[i];
}
require(
verify(_signatures, address(this), signers, _balances, _nonce),
"Invalid signature"
);
_;
}
modifier onlyUser() {
require(isUser[msg.sender], "Not user");
_;
}
function challengeExit(
uint[2] memory _balances,
uint _nonce,
bytes[2] memory _signatures
)
public
onlyUser
checkSignatures(_signatures, _balances, _nonce)
checkBalances(_balances)
{
require(block.timestamp < expiresAt, "Expired challenge period");
require(_nonce > nonce, "Nonce must be greater than the current nonce");
for (uint i = 0; i < _balances.length; i++) {
balances[users[i]] = _balances[i];
}
nonce = _nonce;
expiresAt = block.timestamp.add(challengePeriod);
emit ChallengeExit(msg.sender, nonce);
}
function withdraw() public onlyUser {
require(block.timestamp >= expiresAt, "Challenge period has not expired yet");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Failed to send Ether");
emit Withdraw(msg.sender, amount);
}
}