Skip to content
This repository was archived by the owner on Jan 14, 2025. It is now read-only.

Commit 8fdf8f2

Browse files
committed
Add executeConfirm support to perform multi-sig in 1 transaction
1 parent 0aeff24 commit 8fdf8f2

File tree

1 file changed

+154
-18
lines changed

1 file changed

+154
-18
lines changed

wallet/wallet.sol

Lines changed: 154 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,96 @@
1-
//sol Wallet
1+
// sol Wallet
22
// Multi-sig, daily-limited account proxy/wallet.
33
// @authors:
44
// Gav Wood <[email protected]>
5+
// Ben Chan <[email protected]>
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

Comments
 (0)