| 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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 |
147×
146×
26×
18×
101×
85×
19×
18×
86×
72×
72×
80×
79×
48×
48×
48×
48×
70×
70×
70×
2×
2×
14×
42×
42×
16×
15×
15×
7×
7×
6×
6×
12×
12×
12×
4×
4×
4×
4×
5×
4×
4×
4×
4×
4×
81×
81×
46×
103×
2×
| pragma solidity 0.4.15;
import "./Proxy.sol";
contract MetaIdentityManager {
uint adminTimeLock;
uint userTimeLock;
uint adminRate;
address relay;
event LogIdentityCreated(
address indexed identity,
address indexed creator,
address owner,
address indexed recoveryKey);
event LogOwnerAdded(
address indexed identity,
address indexed owner,
address instigator);
event LogOwnerRemoved(
address indexed identity,
address indexed owner,
address instigator);
event LogRecoveryChanged(
address indexed identity,
address indexed recoveryKey,
address instigator);
event LogMigrationInitiated(
address indexed identity,
address indexed newIdManager,
address instigator);
event LogMigrationCanceled(
address indexed identity,
address indexed newIdManager,
address instigator);
event LogMigrationFinalized(
address indexed identity,
address indexed newIdManager,
address instigator);
mapping(address => mapping(address => uint)) owners;
mapping(address => address) recoveryKeys;
mapping(address => mapping(address => uint)) limiter;
mapping(address => uint) public migrationInitiated;
mapping(address => address) public migrationNewAddress;
modifier onlyAuthorized() {
require(msg.sender == relay || checkMessageData(msg.sender));
_;
}
modifier onlyOwner(address identity, address sender) {
require(isOwner(identity, sender));
_;
}
modifier onlyOlderOwner(address identity, address sender) {
require(isOlderOwner(identity, sender));
_;
}
modifier onlyRecovery(address identity, address sender) {
require(recoveryKeys[identity] == sender);
_;
}
modifier rateLimited(Proxy identity, address sender) {
require(limiter[identity][sender] < (now - adminRate));
limiter[identity][sender] = now;
_;
}
modifier validAddress(address addr) { //protects against some weird attacks
require(addr != address(0));
_;
}
/// @dev Contract constructor sets initial timelocks and meta-tx relay address
/// @param _userTimeLock Time before new owner added by recovery can control proxy
/// @param _adminTimeLock Time before new owner can add/remove owners
/// @param _adminRate Time period used for rate limiting a given key for admin functionality
/// @param _relayAddress Address of meta transaction relay contract
function MetaIdentityManager(uint _userTimeLock, uint _adminTimeLock, uint _adminRate, address _relayAddress) {
adminTimeLock = _adminTimeLock;
userTimeLock = _userTimeLock;
adminRate = _adminRate;
relay = _relayAddress;
}
/// @dev Creates a new proxy contract for an owner and recovery
/// @param owner Key who can use this contract to control proxy. Given full power
/// @param recoveryKey Key of recovery network or address from seed to recovery proxy
/// Gas cost of ~300,000
function createIdentity(address owner, address recoveryKey) public validAddress(recoveryKey) {
Proxy identity = new Proxy();
owners[identity][owner] = now - adminTimeLock; // This is to ensure original owner has full power from day one
recoveryKeys[identity] = recoveryKey;
LogIdentityCreated(identity, msg.sender, owner, recoveryKey);
}
/// @dev Allows a user to transfer control of existing proxy to this contract. Must come through proxy
/// @param owner Key who can use this contract to control proxy. Given full power
/// @param recoveryKey Key of recovery network or address from seed to recovery proxy
/// Note: User must change owner of proxy to this contract after calling this
function registerIdentity(address owner, address recoveryKey) public validAddress(recoveryKey) {
require(recoveryKeys[msg.sender] == 0); // Deny any funny business
owners[msg.sender][owner] = now - adminTimeLock; // Owner has full power from day one
recoveryKeys[msg.sender] = recoveryKey;
LogIdentityCreated(msg.sender, msg.sender, owner, recoveryKey);
}
/// @dev Allows a user to forward a call through their proxy.
function forwardTo(address sender, Proxy identity, address destination, uint value, bytes data) public
onlyAuthorized
onlyOwner(identity, sender)
{
identity.forward(destination, value, data);
}
/// @dev Allows an olderOwner to add a new owner instantly
function addOwner(address sender, Proxy identity, address newOwner) public
onlyAuthorized
onlyOlderOwner(identity, sender)
rateLimited(identity, sender)
{
owners[identity][newOwner] = now - userTimeLock;
LogOwnerAdded(identity, newOwner, sender);
}
/// @dev Allows a recoveryKey to add a new owner with userTimeLock waiting time
function addOwnerFromRecovery(address sender, Proxy identity, address newOwner) public
onlyAuthorized
onlyRecovery(identity, sender)
rateLimited(identity, sender)
{
require(!isOwner(identity, newOwner));
owners[identity][newOwner] = now;
LogOwnerAdded(identity, newOwner, sender);
}
/// @dev Allows an owner to remove another owner instantly
function removeOwner(address sender, Proxy identity, address owner) public
onlyAuthorized
onlyOlderOwner(identity, sender)
rateLimited(identity, sender)
{
delete owners[identity][owner];
LogOwnerRemoved(identity, owner, sender);
}
/// @dev Allows an owner to change the recoveryKey instantly
function changeRecovery(address sender, Proxy identity, address recoveryKey) public
onlyAuthorized
onlyOlderOwner(identity, sender)
rateLimited(identity, sender)
validAddress(recoveryKey)
{
recoveryKeys[identity] = recoveryKey;
LogRecoveryChanged(identity, recoveryKey, sender);
}
/// @dev Allows an owner to begin process of transfering proxy to new IdentityManager
function initiateMigration(address sender, Proxy identity, address newIdManager) public
onlyAuthorized
onlyOlderOwner(identity, sender)
{
migrationInitiated[identity] = now;
migrationNewAddress[identity] = newIdManager;
LogMigrationInitiated(identity, newIdManager, sender);
}
/// @dev Allows an owner to cancel the process of transfering proxy to new IdentityManager
function cancelMigration(address sender, Proxy identity) public
onlyAuthorized
onlyOwner(identity, sender)
{
address canceledManager = migrationNewAddress[identity];
delete migrationInitiated[identity];
delete migrationNewAddress[identity];
LogMigrationCanceled(identity, canceledManager, sender);
}
/// @dev Allows an owner to finalize and completly transfer proxy to new IdentityManager
/// Note: before transfering to a new address, make sure this address is "ready to recieve" the proxy.
/// Not doing so risks the proxy becoming stuck.
function finalizeMigration(address sender, Proxy identity) onlyAuthorized onlyOlderOwner(identity, sender) {
require(migrationInitiated[identity] != 0 && migrationInitiated[identity] + adminTimeLock < now);
address newIdManager = migrationNewAddress[identity];
delete migrationInitiated[identity];
delete migrationNewAddress[identity];
identity.transfer(newIdManager);
LogMigrationFinalized(identity, newIdManager, sender);
}
//Checks that address a is the first input in msg.data.
//Has very minimal gas overhead.
function checkMessageData(address a) internal constant returns (bool t) {
Iif (msg.data.length < 36) return false;
assembly {
let mask := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
t := eq(a, and(mask, calldataload(4)))
}
}
function isOwner(address identity, address owner) public constant returns (bool) {
return (owners[identity][owner] > 0 && (owners[identity][owner] + userTimeLock) <= now);
}
function isOlderOwner(address identity, address owner) public constant returns (bool) {
return (owners[identity][owner] > 0 && (owners[identity][owner] + adminTimeLock) <= now);
}
function isRecovery(address identity, address recoveryKey) public constant returns (bool) {
return recoveryKeys[identity] == recoveryKey;
}
}
|