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

Commit 2085feb

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

File tree

1 file changed

+123
-16
lines changed

1 file changed

+123
-16
lines changed

wallet/wallet.sol

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,33 +138,100 @@ contract multiowned {
138138
uint ownerIndexBit = 2**ownerIndex;
139139
return !(pending.ownersDone & ownerIndexBit == 0);
140140
}
141-
142-
// INTERNAL METHODS
143141

142+
// Gets the next available sequence ID for signing when using confirmAndCheckUsingECRecover
143+
function getNextSequenceId() constant returns (uint) {
144+
uint highestSequenceId = 0;
145+
for (uint i = 0; i < c_maxSequenceIdWindowSize; i++) {
146+
if (m_sequenceIdsUsed[i] > highestSequenceId) {
147+
highestSequenceId = m_sequenceIdsUsed[i];
148+
}
149+
}
150+
return highestSequenceId + 1;
151+
}
152+
153+
// INTERNAL METHODS
154+
// Called within the onlymanyowners modifier.
155+
// Records a confirmation by msg.sender and returns true if the operation has the required number of confirmations
144156
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;
157+
return confirmAndCheckOperationForOwner(_operation, msg.sender);
158+
}
159+
160+
// This operation will look for 2 confirmations
161+
// The first confirmation will be verified using ecrecover
162+
// The second confirmation will be verified using msg.sender
163+
function confirmWithSenderAndECRecover(bytes32 _operation, uint _sequenceId, bytes _signature) internal returns (bool) {
164+
// We expect confirmAndCheckUsingECRecover to run and return false, but mark the pending operation as having 1 confirm
165+
return confirmAndCheckUsingECRecover(_operation, _sequenceId, _signature) || confirmAndCheck(_operation);
166+
}
167+
168+
// Gets an owner using ecrecover, records their confirmation and
169+
// returns true if the operation has the required number of confirmations
170+
function confirmAndCheckUsingECRecover(bytes32 _operation, uint _sequenceId, bytes _signature) internal returns (bool) {
171+
// Verify that the sequence id has not been used before
172+
// Create mapping of the sequence ids being used
173+
uint lowestValueIndex = 0;
174+
for (uint i = 0; i < c_maxSequenceIdWindowSize; i++) {
175+
if (m_sequenceIdsUsed[i] == _sequenceId) {
176+
// This sequence ID has been used before. Disallow!
177+
throw;
178+
}
179+
if (m_sequenceIdsUsed[i] < m_sequenceIdsUsed[lowestValueIndex]) {
180+
lowestValueIndex = i;
181+
}
182+
}
183+
if (_sequenceId < m_sequenceIdsUsed[lowestValueIndex]) {
184+
// The sequence ID being used is lower than the lowest value in the window
185+
// so we cannot accept it as it may have been used before
186+
throw;
187+
}
188+
m_sequenceIdsUsed[lowestValueIndex] = _sequenceId;
189+
190+
// We need to unpack the signature, which is given as an array of 65 bytes (from eth.sign)
191+
bytes32 r;
192+
bytes32 s;
193+
uint8 v;
194+
195+
if (_signature.length != 65)
196+
throw;
197+
198+
assembly {
199+
r := mload(add(_signature, 32))
200+
s := mload(add(_signature, 64))
201+
v := and(mload(add(_signature, 65)), 255)
202+
}
203+
204+
var ownerAddress = ecrecover(_operation, v, r, s);
205+
return confirmAndCheckOperationForOwner(_operation, ownerAddress);
206+
}
207+
208+
// Records confirmations for an operation by the given owner and
209+
// returns true if the operation has the required number of confirmations
210+
function confirmAndCheckOperationForOwner(bytes32 _operation, address _owner) private returns (bool) {
211+
// Determine what index the present sender is
212+
uint ownerIndex = m_ownerIndex[uint(_owner)];
213+
// Make sure they're an owner
214+
if (ownerIndex == 0) return false;
149215

150216
var pending = m_pending[_operation];
151-
// if we're not yet working on this operation, switch over and reset the confirmation status.
217+
// If we're not yet working on this operation, add it
152218
if (pending.yetNeeded == 0) {
153-
// reset count of confirmations needed.
219+
// Reset count of confirmations needed.
154220
pending.yetNeeded = m_required;
155-
// reset which owners have confirmed (none) - set our bitmap to 0.
221+
// Reset which owners have confirmed (none) - set our bitmap to 0.
156222
pending.ownersDone = 0;
157223
pending.index = m_pendingIndex.length++;
158224
m_pendingIndex[pending.index] = _operation;
159225
}
160-
// determine the bit to set for this owner.
226+
227+
// Determine the bit to set for this owner on the pending state for the operation
161228
uint ownerIndexBit = 2**ownerIndex;
162-
// make sure we (the message sender) haven't confirmed this operation previously.
229+
// Make sure the owner has not confirmed this operation previously.
163230
if (pending.ownersDone & ownerIndexBit == 0) {
164-
Confirmation(msg.sender, _operation);
165-
// ok - check if count is enough to go ahead.
231+
Confirmation(_owner, _operation);
232+
// Check if this confirmation puts us at the required number of needed confirmations.
166233
if (pending.yetNeeded <= 1) {
167-
// enough confirmations: reset and run interior.
234+
// Enough confirmations: mark operation as passed and return true to continue execution
168235
delete m_pendingIndex[m_pending[_operation].index];
169236
delete m_pending[_operation];
170237
return true;
@@ -176,6 +243,8 @@ contract multiowned {
176243
pending.ownersDone |= ownerIndexBit;
177244
}
178245
}
246+
247+
return false;
179248
}
180249

181250
function reorganizeOwners() private {
@@ -216,6 +285,15 @@ contract multiowned {
216285
// the ongoing operations.
217286
mapping(bytes32 => PendingState) m_pending;
218287
bytes32[] m_pendingIndex;
288+
289+
// When we use ecrecover to verify signatures (in addition to msg.sender), an array window of sequence ids is used.
290+
// This prevents from replay attacks by the first signer.
291+
//
292+
// Sequence IDs may not be repeated and should start from 1 onwards. Stores the last 10 largest sequence ids in a window
293+
// New sequence ids being added must replace the smallest of those numbers and must be larger than the smallest value stored.
294+
// This allows some degree of flexibility for submission of multiple transactions in a block.
295+
uint constant c_maxSequenceIdWindowSize = 10;
296+
uint[10] m_sequenceIdsUsed;
219297
}
220298

221299
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
@@ -363,9 +441,38 @@ contract Wallet is multisig, multiowned, daylimit {
363441
return true;
364442
}
365443
}
366-
444+
445+
// Execute and confirm a transaction with 2 signatures - one using the msg.sender and another using ecrecover
446+
// The signature is a signed form (using eth.sign) of tightly packed to, value, data, expiretime and sequenceId
447+
// Sequence IDs are numbers starting from 1. They used to prevent replay attacks and may not be repeated.
448+
function executeAndConfirm(address _to, uint _value, bytes _data, uint _expireTime, uint _sequenceId, bytes _signature)
449+
external onlyowner
450+
returns (bytes32)
451+
{
452+
if (_expireTime < block.timestamp) {
453+
throw;
454+
}
455+
456+
// The unique hash is the combination of all arguments except the signature
457+
var operationHash = sha3(_to, _value, _data, _expireTime, _sequenceId);
458+
459+
// Confirm the operation
460+
if (confirmWithSenderAndECRecover(operationHash, _sequenceId, _signature)) {
461+
if (!(_to.call.value(_value)(_data))) {
462+
throw;
463+
}
464+
MultiTransact(msg.sender, operationHash, _value, _to, _data);
465+
return 0;
466+
}
467+
468+
m_txs[operationHash].to = _to;
469+
m_txs[operationHash].value = _value;
470+
m_txs[operationHash].data = _data;
471+
ConfirmationNeeded(operationHash, msg.sender, _value, _to, _data);
472+
return operationHash;
473+
}
474+
367475
// INTERNAL METHODS
368-
369476
function clearPending() internal {
370477
uint length = m_pendingIndex.length;
371478
for (uint i = 0; i < length; ++i)

0 commit comments

Comments
 (0)