-
Notifications
You must be signed in to change notification settings - Fork 39
/
DharmaKeyRegistryV2.sol
184 lines (157 loc) · 7.04 KB
/
DharmaKeyRegistryV2.sol
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
pragma solidity 0.5.11;
import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "../helpers/TwoStepOwnable.sol";
import "../../interfaces/DharmaKeyRegistryInterface.sol";
/**
* @title DharmaKeyRegistryV2
* @author 0age
* @notice The Dharma Key Registry is an owned contract that holds the public
* user signing keys that will be used by the Dharma Smart Wallet. Each time a
* particular Dharma Smart Wallet instance needs to validate a signature, it
* will first retrieve the public address for the secondary signing key
* associated with that wallet from the Dharma Key Registry. If a specific key
* has not been set for that smart wallet, it will return the global public key.
* Otherwise, it will return the specific signing key. Additional view functions
* are also provided for retrieving public keys directly. Only the owner may
* update these keys. Also, note that the V2 key registry includes an additional
* mapping to track all keys that have been used, and only allows a given key to
* be set one time.
*/
contract DharmaKeyRegistryV2 is TwoStepOwnable, DharmaKeyRegistryInterface {
using ECDSA for bytes32;
// The global public key serves as the default signing key.
address private _globalKey;
// Specific keys may also be set on a per-caller basis.
mapping (address => address) private _specificKeys;
// Maintain a mapping of all used keys (to prevent reuse).
mapping (address => bool) private _usedKeys;
/**
* @notice In the constructor, set the initial global key and the initial
* owner to tx.origin.
*/
constructor() public {
// Initially set the global key to the account of the transaction submitter.
_registerGlobalKey(tx.origin);
}
/**
* @notice Set a new global key. This method may only be called by the owner,
* and a signature must also be provided in order to verify that the provided
* global public key has a corresponding private key that can be used to sign
* messages.
* @param globalKey address The new global public key.
* @param signature bytes A signature of a message hash containing the address
* of this contract, the new global key, and a specific message, that must
* resolve to the supplied global key.
*/
function setGlobalKey(
address globalKey, bytes calldata signature
) external onlyOwner {
// Ensure that the provided global key is not the null address.
require(globalKey != address(0), "A global key must be supplied.");
// Message hash constructed according to EIP-191-0x45 to prevent replays.
bytes32 messageHash = keccak256(
abi.encodePacked(
address(this),
globalKey,
"This signature demonstrates that the supplied signing key is valid."
)
);
// Recover the signer of the message hash using the provided signature.
address signer = messageHash.toEthSignedMessageHash().recover(signature);
// Ensure that the provided signature resolves to the provided global key.
require(globalKey == signer, "Invalid signature for supplied global key.");
// Update global key to the provided global key and prevent future reuse.
_registerGlobalKey(globalKey);
}
/**
* @notice Set a new specific key for a particular account. This method may
* only be called by the owner. Signatures are not required in order to make
* setting specific keys more efficient at scale. Providing the null address
* for the specific key will remove a specific key from the given account.
* @param account address The account to set the new specific public key for.
* @param specificKey address The new specific public key.
*/
function setSpecificKey(
address account, address specificKey
) external onlyOwner {
// Ensure that the key has not been used previously.
require(!_usedKeys[specificKey], "Key has been used previously.");
// Emit an event signifying that the specific key has been modified.
emit NewSpecificKey(account, _specificKeys[account], specificKey);
// Update specific key for provided account to the provided specific key.
_specificKeys[account] = specificKey;
// Mark the key as having been used previously.
_usedKeys[specificKey] = true;
}
/**
* @notice Get the public key associated with the caller of this function. If
* a specific key is set for the caller, it will be returned; otherwise, the
* global key will be returned.
* @return The public key to use for the caller.
*/
function getKey() external view returns (address key) {
// Retrieve the specific key, if any, for the caller.
key = _specificKeys[msg.sender];
// Fall back to the global key in the event that no specific key is set.
if (key == address(0)) {
key = _globalKey;
}
}
/**
* @notice Get the public key associated with a particular account. If a
* specific key is set for the account, it will be returned; otherwise, the
* global key will be returned.
* @param account address The account to find the public key for.
* @return The public key to use for the provided account.
*/
function getKeyForUser(address account) external view returns (address key) {
// Retrieve the specific key, if any, for the specified account.
key = _specificKeys[account];
// Fall back to the global key in the event that no specific key is set.
if (key == address(0)) {
key = _globalKey;
}
}
/**
* @notice Get the global public key.
* @return The global public key.
*/
function getGlobalKey() external view returns (address globalKey) {
// Retrieve and return the global key.
globalKey = _globalKey;
}
/**
* @notice Get the specific public key associated with the supplied account.
* The call will revert if a specific public key is not set for the account.
* @param account address The account to find the specific public key for.
* @return The specific public key set on the provided account, if one exists.
*/
function getSpecificKey(
address account
) external view returns (address specificKey) {
// Retrieve the specific key, if any, for the account.
specificKey = _specificKeys[account];
// Revert in the event that there is no specific key set.
require(
specificKey != address(0),
"No specific key set for the provided account."
);
}
/**
* @notice Internal function to set a new global key once contract ownership
* and signature validity have both been checked, or during contract creation.
* The provided global key must not have been used previously, and once set it
* will be registered as having been used.
* @param globalKey address The new global public key.
*/
function _registerGlobalKey(address globalKey) internal {
// Ensure that the key has not been used previously.
require(!_usedKeys[globalKey], "Key has been used previously.");
// Emit an event signifying that the global key has been modified.
emit NewGlobalKey(_globalKey, globalKey);
// Update the global key to the provided global key.
_globalKey = globalKey;
// Mark the key as having been used previously.
_usedKeys[globalKey] = true;
}
}