1- //sol Wallet
1+ // sol Wallet
22// Multi-sig, daily-limited account proxy/wallet.
33// @authors:
445+ 6+
7+ // Inheritable contract with methods that can be used to help with the pattern of using ecrecover to obtain a signing
8+ // address from a signature in a data field for the purposes of confirming an operation
9+ contract ecverifiable {
10+ // When we use ecrecover to verify signatures (in addition to msg.sender), an array window of sequence ids is used.
11+ // This prevents from replay attacks by the first signer.
12+ //
13+ // Sequence IDs may not be repeated and should start from 1 onwards. Stores the last 10 largest sequence ids in a window
14+ // New sequence ids being added must replace the smallest of those numbers and must be larger than the smallest value stored.
15+ // This allows some degree of flexibility for submission of multiple transactions in a block.
16+ uint constant SEQUENCE_ID_WINDOW_SIZE = 10 ;
17+ uint [10 ] recentSequenceIds;
18+
19+ // Gets the next available sequence ID for signing when using confirmAndCheckUsingECRecover
20+ function getNextSequenceId () returns (uint ) {
21+ uint highestSequenceId = 0 ;
22+ for (uint i = 0 ; i < SEQUENCE_ID_WINDOW_SIZE; i++ ) {
23+ if (recentSequenceIds[i] > highestSequenceId) {
24+ highestSequenceId = recentSequenceIds[i];
25+ }
26+ }
27+ return highestSequenceId + 1 ;
28+ }
29+
30+ // Given the sequence id, operation hash and signature, verify the sequence id against replay attacks and then
31+ // recover the signing address
32+ function ecVerifyAndRecover (bytes32 operation , uint sequenceId , bytes signature ) internal returns (address ) {
33+ tryInsertSequenceId (sequenceId);
34+ return recoverAddressFromSignature (operation, signature);
35+ }
36+
37+ /**
38+ * Gets a signer's address using ecrecover
39+ * @param operationHash the sha3 of the operation to verify
40+ * @param signature the tightly packed signature of r, s, and v as an array of 65 bytes (returned by eth.sign)
41+ */
42+ function recoverAddressFromSignature (bytes32 operationHash , bytes signature ) private returns (address ) {
43+ if (signature.length != 65 ) {
44+ throw ;
45+ }
46+ // We need to unpack the signature, which is given as an array of 65 bytes (from eth.sign)
47+ bytes32 r;
48+ bytes32 s;
49+ uint8 v;
50+ assembly {
51+ r := mload (add (signature, 32 ))
52+ s := mload (add (signature, 64 ))
53+ v := and (mload (add (signature, 65 )), 255 )
54+ }
55+ if (v < 27 ) {
56+ v += 27 ; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs
57+ }
58+ return ecrecover (operationHash, v, r, s);
59+ }
60+
61+ /**
62+ * Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.
63+ * We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and
64+ * greater than the minimum element in the window.
65+ */
66+ function tryInsertSequenceId (uint sequenceId ) private returns (uint ) {
67+ // Keep a pointer to the lowest value element in the window
68+ uint lowestValueIndex = 0 ;
69+ for (uint i = 0 ; i < SEQUENCE_ID_WINDOW_SIZE; i++ ) {
70+ if (recentSequenceIds[i] == sequenceId) {
71+ // This sequence ID has been used before. Disallow!
72+ throw ;
73+ }
74+ if (recentSequenceIds[i] < recentSequenceIds[lowestValueIndex]) {
75+ lowestValueIndex = i;
76+ }
77+ }
78+ if (sequenceId < recentSequenceIds[lowestValueIndex]) {
79+ // The sequence ID being used is lower than the lowest value in the window
80+ // so we cannot accept it as it may have been used before
81+ throw ;
82+ }
83+ recentSequenceIds[lowestValueIndex] = sequenceId;
84+ }
85+ }
86+
587// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
688// single, or, crucially, each of a number of, designated owners.
789// usage:
890// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
991// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
1092// interior is executed.
11- contract multiowned {
93+ contract multiowned is ecverifiable {
1294
1395 // TYPES
1496
@@ -138,33 +220,56 @@ contract multiowned {
138220 uint ownerIndexBit = 2 ** ownerIndex;
139221 return ! (pending.ownersDone & ownerIndexBit == 0 );
140222 }
141-
142- // INTERNAL METHODS
143223
224+ // INTERNAL METHODS
225+ // Called within the onlymanyowners modifier.
226+ // Records a confirmation by msg.sender and returns true if the operation has the required number of confirmations
144227 function confirmAndCheck (bytes32 _operation ) internal returns (bool ) {
145- // determine what index the present sender is:
146- uint ownerIndex = m_ownerIndex[uint (msg .sender )];
147- // make sure they're an owner
148- if (ownerIndex == 0 ) return ;
228+ return confirmAndCheckOperationForOwner (_operation, msg .sender );
229+ }
230+
231+ // This operation will look for 2 confirmations
232+ // The first confirmation will be verified using ecrecover
233+ // The second confirmation will be verified using msg.sender
234+ function confirmWithSenderAndECRecover (bytes32 _operation , uint _sequenceId , bytes _signature ) internal returns (bool ) {
235+ // We expect confirmAndCheckUsingECRecover to run and return false, but mark the pending operation as having 1 confirm
236+ return confirmAndCheckUsingECRecover (_operation, _sequenceId, _signature) || confirmAndCheck (_operation);
237+ }
238+
239+ // Gets an owner using ecrecover, records their confirmation and
240+ // returns true if the operation has the required number of confirmations
241+ function confirmAndCheckUsingECRecover (bytes32 _operation , uint _sequenceId , bytes _signature ) internal returns (bool ) {
242+ var ownerAddress = ecVerifyAndRecover (_operation, _sequenceId, _signature);
243+ return confirmAndCheckOperationForOwner (_operation, ownerAddress);
244+ }
245+
246+ // Records confirmations for an operation by the given owner and
247+ // returns true if the operation has the required number of confirmations
248+ function confirmAndCheckOperationForOwner (bytes32 _operation , address _owner ) private returns (bool ) {
249+ // Determine what index the present sender is
250+ uint ownerIndex = m_ownerIndex[uint (_owner)];
251+ // Make sure they're an owner
252+ if (ownerIndex == 0 ) return false ;
149253
150254 var pending = m_pending[_operation];
151- // if we're not yet working on this operation, switch over and reset the confirmation status.
255+ // If we're not yet working on this operation, add it
152256 if (pending.yetNeeded == 0 ) {
153- // reset count of confirmations needed.
257+ // Reset count of confirmations needed.
154258 pending.yetNeeded = m_required;
155- // reset which owners have confirmed (none) - set our bitmap to 0.
259+ // Reset which owners have confirmed (none) - set our bitmap to 0.
156260 pending.ownersDone = 0 ;
157261 pending.index = m_pendingIndex.length ++ ;
158262 m_pendingIndex[pending.index] = _operation;
159263 }
160- // determine the bit to set for this owner.
264+
265+ // Determine the bit to set for this owner on the pending state for the operation
161266 uint ownerIndexBit = 2 ** ownerIndex;
162- // make sure we ( the message sender) haven't confirmed this operation previously.
267+ // Make sure the owner has not confirmed this operation previously.
163268 if (pending.ownersDone & ownerIndexBit == 0 ) {
164- Confirmation (msg . sender , _operation);
165- // ok - check if count is enough to go ahead .
269+ Confirmation (_owner , _operation);
270+ // Check if this confirmation puts us at the required number of needed confirmations .
166271 if (pending.yetNeeded <= 1 ) {
167- // enough confirmations: reset and run interior.
272+ // Enough confirmations: mark operation as passed and return true to continue execution
168273 delete m_pendingIndex[m_pending[_operation].index];
169274 delete m_pending[_operation];
170275 return true ;
@@ -176,6 +281,8 @@ contract multiowned {
176281 pending.ownersDone |= ownerIndexBit;
177282 }
178283 }
284+
285+ return false ;
179286 }
180287
181288 function reorganizeOwners () private {
@@ -363,9 +470,38 @@ contract Wallet is multisig, multiowned, daylimit {
363470 return true ;
364471 }
365472 }
366-
473+
474+ // Execute and confirm a transaction with 2 signatures - one using the msg.sender and another using ecrecover
475+ // The signature is a signed form (using eth.sign) of tightly packed to, value, data, expiretime and sequenceId
476+ // Sequence IDs are numbers starting from 1. They used to prevent replay attacks and may not be repeated.
477+ function executeAndConfirm (address _to , uint _value , bytes _data , uint _expireTime , uint _sequenceId , bytes _signature )
478+ external onlyowner
479+ returns (bytes32 )
480+ {
481+ if (_expireTime < block .timestamp ) {
482+ throw ;
483+ }
484+
485+ // The unique hash is the combination of all arguments except the signature
486+ var operationHash = sha3 (_to, _value, _data, _expireTime, _sequenceId);
487+
488+ // Confirm the operation
489+ if (confirmWithSenderAndECRecover (operationHash, _sequenceId, _signature)) {
490+ if (! (_to.call.value (_value)(_data))) {
491+ throw ;
492+ }
493+ MultiTransact (msg .sender , operationHash, _value, _to, _data);
494+ return 0 ;
495+ }
496+
497+ m_txs[operationHash].to = _to;
498+ m_txs[operationHash].value = _value;
499+ m_txs[operationHash].data = _data;
500+ ConfirmationNeeded (operationHash, msg .sender , _value, _to, _data);
501+ return operationHash;
502+ }
503+
367504 // INTERNAL METHODS
368-
369505 function clearPending () internal {
370506 uint length = m_pendingIndex.length ;
371507 for (uint i = 0 ; i < length; ++ i)
0 commit comments