commit
ef5336404a
|
@ -9,6 +9,11 @@ trim_trailing_whitespace=true
|
|||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
|
||||
[*.sol]
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
tab_size=4
|
||||
|
||||
[*.js]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
|
|
@ -18,6 +18,51 @@ library Authorities {
|
|||
}
|
||||
}
|
||||
|
||||
/// Library used only to test Signer library via rpc calls
|
||||
library SignerTest {
|
||||
function signer (bytes signature, bytes message) constant returns (address) {
|
||||
return Signer.signer(signature, message);
|
||||
}
|
||||
}
|
||||
|
||||
library Utils {
|
||||
function toString (uint256 v) internal returns (string str) {
|
||||
// it is used only for small numbers
|
||||
bytes memory reversed = new bytes(8);
|
||||
uint i = 0;
|
||||
while (v != 0) {
|
||||
uint remainder = v % 10;
|
||||
v = v / 10;
|
||||
reversed[i++] = byte(48 + remainder);
|
||||
}
|
||||
bytes memory s = new bytes(i);
|
||||
for (uint j = 0; j < i; j++) {
|
||||
s[j] = reversed[i - j - 1];
|
||||
}
|
||||
str = string(s);
|
||||
}
|
||||
}
|
||||
|
||||
library Signer {
|
||||
function signer (bytes signature, bytes message) internal returns (address) {
|
||||
require(signature.length == 65);
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
bytes1 v;
|
||||
assembly {
|
||||
r := mload(add(signature, 0x20))
|
||||
s := mload(add(signature, 0x40))
|
||||
v := mload(add(signature, 0x60))
|
||||
}
|
||||
return ecrecover(hash(message), uint8(v), r, s);
|
||||
}
|
||||
|
||||
function hash (bytes message) internal returns (bytes32) {
|
||||
bytes memory prefix = "\x19Ethereum Signed Message:\n";
|
||||
return sha3(prefix, Utils.toString(message.length), message);
|
||||
}
|
||||
}
|
||||
|
||||
contract EthereumBridge {
|
||||
using Authorities for address[];
|
||||
|
||||
|
@ -40,8 +85,7 @@ contract EthereumBridge {
|
|||
|
||||
/// Multisig authority validation
|
||||
modifier allAuthorities (uint8[] v, bytes32[] r, bytes32[] s, bytes message) {
|
||||
bytes memory prefix = "\x19Ethereum Signed Message:\n";
|
||||
var hash = sha3(prefix, message.length, message);
|
||||
var hash = Signer.hash(message);
|
||||
var used = new address[](requiredSignatures);
|
||||
|
||||
require(requiredSignatures <= v.length);
|
||||
|
@ -57,7 +101,7 @@ contract EthereumBridge {
|
|||
|
||||
/// Constructor.
|
||||
function EthereumBridge (uint n, address[] a) {
|
||||
require(n != 0);
|
||||
require(n != 0);
|
||||
require(n <= a.length);
|
||||
requiredSignatures = n;
|
||||
authorities = a;
|
||||
|
@ -79,9 +123,9 @@ contract EthereumBridge {
|
|||
uint value;
|
||||
bytes32 hash;
|
||||
assembly {
|
||||
recipient := mload(message)
|
||||
value := mload(add(message, 0x32))
|
||||
hash := mload(add(message, 0x64))
|
||||
recipient := mload(add(message, 0x20))
|
||||
value := mload(add(message, 0x40))
|
||||
hash := mload(add(message, 0x60))
|
||||
}
|
||||
|
||||
// Duplicated withdraw
|
||||
|
@ -92,35 +136,6 @@ contract EthereumBridge {
|
|||
recipient.transfer(value);
|
||||
Withdraw(recipient, value);
|
||||
}
|
||||
|
||||
/// Used to elect new authorities.
|
||||
///
|
||||
// message contains:
|
||||
// new requiredSignatures (uint)
|
||||
// new number of authorities (uint)
|
||||
// new authorities (bytes20)
|
||||
function reelect (uint8[] v, bytes32[] r, bytes32[] s, bytes message) allAuthorities(v, r, s, message) {
|
||||
uint newRequiredSignatures;
|
||||
uint newAuthoritiesNumber;
|
||||
address addressPtr;
|
||||
|
||||
assembly {
|
||||
newRequiredSignatures := mload(message)
|
||||
newAuthoritiesNumber := mload(add(message, 0x32))
|
||||
}
|
||||
|
||||
require(newRequiredSignatures <= newAuthoritiesNumber);
|
||||
|
||||
authorities.truncate(newAuthoritiesNumber);
|
||||
|
||||
for (uint i = 0; i < newAuthoritiesNumber; i++) {
|
||||
assembly {
|
||||
let offset := add(0x64, mul(0x32, i))
|
||||
addressPtr := mload(add(message, offset))
|
||||
}
|
||||
authorities[i] = addressPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract KovanBridge {
|
||||
|
@ -166,7 +181,7 @@ contract KovanBridge {
|
|||
|
||||
/// Constructor.
|
||||
function KovanBridge(uint n, address[] a) {
|
||||
require(n != 0);
|
||||
require(n != 0);
|
||||
require(n <= a.length);
|
||||
requiredSignatures = n;
|
||||
authorities = a;
|
||||
|
@ -184,8 +199,8 @@ contract KovanBridge {
|
|||
/// deposit value (uint)
|
||||
/// mainnet transaction hash (bytes32) // to avoid transaction duplication
|
||||
function deposit (address recipient, uint value, bytes32 transactionHash) onlyAuthority() {
|
||||
// Protection from misbehaing authority
|
||||
var hash = sha3(recipient, value, transactionHash);
|
||||
// Protection from misbehaing authority
|
||||
var hash = sha3(recipient, value, transactionHash);
|
||||
|
||||
// Duplicated deposits
|
||||
require(!deposits[hash].contains(msg.sender));
|
||||
|
@ -220,10 +235,11 @@ contract KovanBridge {
|
|||
/// withdrawal value (uint)
|
||||
/// kovan transaction hash (bytes32) // to avoid transaction duplication
|
||||
function submitSignature (bytes signature, bytes message) onlyAuthority() {
|
||||
// Valid signature must have 65 bytes
|
||||
require(signature.length == 65);
|
||||
// Valid withdraw message must have 84 bytes
|
||||
require(message.length == 84);
|
||||
// Validate submited signatures
|
||||
require(Signer.signer(signature, message) == msg.sender);
|
||||
|
||||
// Valid withdraw message must have 84 bytes
|
||||
require(message.length == 84);
|
||||
var hash = sha3(message);
|
||||
|
||||
// Duplicated signatures
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
var Signer = artifacts.require("SignerTest");
|
||||
|
||||
contract("Signer", function() {
|
||||
it("should validate signature", function() {
|
||||
var signature = "0xb585c41f3cceb2ff9b5c033f2edbefe93415bde365489c989bad8cef3b18e38148a13e100608a29735d709fe708926d37adcecfffb32b1d598727028a16df5db1b";
|
||||
var message = "0xdeadbeaf";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return Signer.new().then(function(instance) {
|
||||
return instance.signer.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate signature for long message", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b1c";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return Signer.new().then(function(instance) {
|
||||
return instance.signer.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
})
|
|
@ -85,7 +85,7 @@ contract('KovanBridge', function(accounts) {
|
|||
})
|
||||
})
|
||||
|
||||
it("should ignore misbehaving authority", function() {
|
||||
it("should ignore misbehaving authority when confirming deposit", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1], accounts[2]];
|
||||
|
@ -107,11 +107,283 @@ contract('KovanBridge', function(accounts) {
|
|||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
|
||||
assert.equal(user_account, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
assert.equal(value, result.logs[0].args.value, "Event value should match deposited ether");
|
||||
assert.equal(value, result.logs[0].args.value, "Event value should match transaction value");
|
||||
return meta.balances.call(user_account);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow user to transfer value internally", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var user_account2 = accounts[3];
|
||||
var value = web3.toWei(3, "ether");
|
||||
var value2 = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(user_account2, value2, false, { from: user_account });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
|
||||
assert.equal(user_account, result.logs[0].args.from, "Event from should be transaction sender");
|
||||
assert.equal(user_account2, result.logs[0].args.to, "Event from should be transaction recipient");
|
||||
assert.equal(value2, result.logs[0].args.value, "Event value should match transaction value");
|
||||
return Promise.all([
|
||||
meta.balances.call(user_account),
|
||||
meta.balances.call(user_account2)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result[0]);
|
||||
assert.equal(web3.toWei(1, "ether"), result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow user to transfer value", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var user_account2 = accounts[3];
|
||||
var value = web3.toWei(3, "ether");
|
||||
var value2 = web3.toWei(4, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(user_account2, value2, false, { from: user_account });
|
||||
}).then(function(result) {
|
||||
assert(false, "Transfer should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow user to trigger withdraw", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var user_account2 = accounts[3];
|
||||
var value = web3.toWei(3, "ether");
|
||||
var value2 = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(user_account2, value2, true, { from: user_account });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Withdraw", result.logs[0].event, "Event name should be Withdraw");
|
||||
assert.equal(user_account2, result.logs[0].args.recipient, "Event recipient should be equal to transaction recipient");
|
||||
assert.equal(value2, result.logs[0].args.value, "Event value should match transaction value");
|
||||
return Promise.all([
|
||||
meta.balances.call(user_account),
|
||||
meta.balances.call(user_account2)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result[0]);
|
||||
assert.equal(web3.toWei(0, "ether"), result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
function sign(address, data) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
web3.eth.sign(address, data, function(err, result) {
|
||||
if (err !== null) {
|
||||
return reject(err);
|
||||
} else {
|
||||
return resolve(normalizeSignature(result));
|
||||
//return resolve(result);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// geth && testrpc has different output of eth_sign than parity
|
||||
// https://github.com/ethereumjs/testrpc/issues/243#issuecomment-326750236
|
||||
function normalizeSignature(signature) {
|
||||
// strip 0x
|
||||
signature = signature.substr(2);
|
||||
|
||||
// increase v by 27...
|
||||
return "0x" + signature.substr(0, 128) + (parseInt(signature.substr(128), 16) + 27).toString(16);
|
||||
}
|
||||
|
||||
it("should successfully submit signature and trigger CollectedSignatures event", function() {
|
||||
var meta;
|
||||
var signature;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
signature = result;
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
|
||||
assert.equal(authorities[0], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
|
||||
return Promise.all([
|
||||
meta.signature.call(result.logs[0].args.messageHash, 0),
|
||||
meta.message(result.logs[0].args.messageHash),
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(signature, result[0]);
|
||||
assert.equal(message, result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
it("should successfully submit signature but not trigger CollectedSignatures event", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result.logs.length, "No events should be created");
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to collect signatures for multiple events in parallel", function() {
|
||||
var meta;
|
||||
var signatures_for_message = [];
|
||||
var signatures_for_message2 = [];
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var message2 = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return Promise.all([
|
||||
sign(authorities[0], message),
|
||||
sign(authorities[1], message),
|
||||
sign(authorities[0], message2),
|
||||
sign(authorities[1], message2),
|
||||
]);
|
||||
}).then(function(result) {
|
||||
signatures_for_message.push(result[0]);
|
||||
signatures_for_message.push(result[1]);
|
||||
signatures_for_message2.push(result[2]);
|
||||
signatures_for_message2.push(result[3]);
|
||||
return meta.submitSignature(signatures_for_message[0], message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result.logs.length, "No events should be created");
|
||||
return meta.submitSignature(signatures_for_message2[1], message2, { from: authorities[1] });
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result.logs.length, "No events should be created");
|
||||
return meta.submitSignature(signatures_for_message2[0], message2, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
|
||||
assert.equal(authorities[0], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
|
||||
return Promise.all([
|
||||
meta.signature.call(result.logs[0].args.messageHash, 0),
|
||||
meta.signature.call(result.logs[0].args.messageHash, 1),
|
||||
meta.message(result.logs[0].args.messageHash),
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(signatures_for_message2[1], result[0]);
|
||||
assert.equal(signatures_for_message2[0], result[1]);
|
||||
assert.equal(message2, result[2]);
|
||||
return meta.submitSignature(signatures_for_message[1], message, { from: authorities[1] });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
|
||||
assert.equal(authorities[1], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
|
||||
return Promise.all([
|
||||
meta.signature.call(result.logs[0].args.messageHash, 0),
|
||||
meta.signature.call(result.logs[0].args.messageHash, 1),
|
||||
meta.message(result.logs[0].args.messageHash),
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(signatures_for_message[0], result[0]);
|
||||
assert.equal(signatures_for_message[1], result[1]);
|
||||
assert.equal(message, result[2]);
|
||||
})
|
||||
})
|
||||
|
||||
it("should not be possible to submit to short message", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert(false, "submitSignature should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
it("should not be possible to submit different message then the signed one", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var message2 = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message2, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert(false, "submitSignature should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
it("should not be possible to submit signature signed by different authority", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[1] });
|
||||
}).then(function(result) {
|
||||
assert(false, "submitSignature should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
it("should not be possible to submit signature twice", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 0;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return KovanBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert(false, "submitSignature should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue