147 lines
5.5 KiB
Solidity
147 lines
5.5 KiB
Solidity
pragma solidity 0.4.24;
|
|
import "../interfaces/IBridgeValidators.sol";
|
|
|
|
library Message {
|
|
function addressArrayContains(address[] array, address value) internal pure returns (bool) {
|
|
for (uint256 i = 0; i < array.length; i++) {
|
|
if (array[i] == value) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// layout of message :: bytes:
|
|
// offset 0: 32 bytes :: uint256 - message length
|
|
// offset 32: 20 bytes :: address - recipient address
|
|
// offset 52: 32 bytes :: uint256 - value
|
|
// offset 84: 32 bytes :: bytes32 - transaction hash
|
|
// offset 116: 20 bytes :: address - contract address to prevent double spending
|
|
|
|
// mload always reads 32 bytes.
|
|
// so we can and have to start reading recipient at offset 20 instead of 32.
|
|
// if we were to read at 32 the address would contain part of value and be corrupted.
|
|
// when reading from offset 20 mload will read 12 bytes (most of them zeros) followed
|
|
// by the 20 recipient address bytes and correctly convert it into an address.
|
|
// this saves some storage/gas over the alternative solution
|
|
// which is padding address to 32 bytes and reading recipient at offset 32.
|
|
// for more details see discussion in:
|
|
// https://github.com/paritytech/parity-bridge/issues/61
|
|
function parseMessage(bytes message)
|
|
internal
|
|
pure
|
|
returns (address recipient, uint256 amount, bytes32 txHash, address contractAddress)
|
|
{
|
|
require(isMessageValid(message));
|
|
assembly {
|
|
recipient := mload(add(message, 20))
|
|
amount := mload(add(message, 52))
|
|
txHash := mload(add(message, 84))
|
|
contractAddress := mload(add(message, 104))
|
|
}
|
|
}
|
|
|
|
function isMessageValid(bytes _msg) internal pure returns (bool) {
|
|
return _msg.length == requiredMessageLength();
|
|
}
|
|
|
|
function requiredMessageLength() internal pure returns (uint256) {
|
|
return 104;
|
|
}
|
|
|
|
function recoverAddressFromSignedMessage(bytes signature, bytes message, bool isAMBMessage)
|
|
internal
|
|
pure
|
|
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))
|
|
}
|
|
require(uint8(v) == 27 || uint8(v) == 28);
|
|
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0);
|
|
|
|
return ecrecover(hashMessage(message, isAMBMessage), uint8(v), r, s);
|
|
}
|
|
|
|
function hashMessage(bytes message, bool isAMBMessage) internal pure returns (bytes32) {
|
|
bytes memory prefix = "\x19Ethereum Signed Message:\n";
|
|
if (isAMBMessage) {
|
|
return keccak256(abi.encodePacked(prefix, uintToString(message.length), message));
|
|
} else {
|
|
string memory msgLength = "104";
|
|
return keccak256(abi.encodePacked(prefix, msgLength, message));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Validates provided signatures, only first requiredSignatures() number
|
|
* of signatures are going to be validated, these signatures should be from different validators.
|
|
* @param _message bytes message used to generate signatures
|
|
* @param _signatures bytes blob with signatures to be validated.
|
|
* First byte X is a number of signatures in a blob,
|
|
* next X bytes are v components of signatures,
|
|
* next 32 * X bytes are r components of signatures,
|
|
* next 32 * X bytes are s components of signatures.
|
|
* @param _validatorContract contract, which conforms to the IBridgeValidators interface,
|
|
* where info about current validators and required signatures is stored.
|
|
* @param isAMBMessage true if _message is an AMB message with arbitrary length.
|
|
*/
|
|
function hasEnoughValidSignatures(
|
|
bytes _message,
|
|
bytes _signatures,
|
|
IBridgeValidators _validatorContract,
|
|
bool isAMBMessage
|
|
) internal view {
|
|
require(isAMBMessage || isMessageValid(_message));
|
|
uint256 requiredSignatures = _validatorContract.requiredSignatures();
|
|
uint256 amount;
|
|
assembly {
|
|
amount := and(mload(add(_signatures, 1)), 0xff)
|
|
}
|
|
require(amount >= requiredSignatures);
|
|
bytes32 hash = hashMessage(_message, isAMBMessage);
|
|
address[] memory encounteredAddresses = new address[](requiredSignatures);
|
|
|
|
for (uint256 i = 0; i < requiredSignatures; i++) {
|
|
uint8 v;
|
|
bytes32 r;
|
|
bytes32 s;
|
|
uint256 posr = 33 + amount + 32 * i;
|
|
uint256 poss = posr + 32 * amount;
|
|
assembly {
|
|
v := mload(add(_signatures, add(2, i)))
|
|
r := mload(add(_signatures, posr))
|
|
s := mload(add(_signatures, poss))
|
|
}
|
|
|
|
address recoveredAddress = ecrecover(hash, v, r, s);
|
|
require(_validatorContract.isValidator(recoveredAddress));
|
|
require(!addressArrayContains(encounteredAddresses, recoveredAddress));
|
|
encounteredAddresses[i] = recoveredAddress;
|
|
}
|
|
}
|
|
|
|
function uintToString(uint256 i) internal pure returns (string) {
|
|
if (i == 0) return "0";
|
|
uint256 j = i;
|
|
uint256 length;
|
|
while (j != 0) {
|
|
length++;
|
|
j /= 10;
|
|
}
|
|
bytes memory bstr = new bytes(length);
|
|
uint256 k = length - 1;
|
|
while (i != 0) {
|
|
bstr[k--] = bytes1(48 + (i % 10));
|
|
i /= 10;
|
|
}
|
|
return string(bstr);
|
|
}
|
|
}
|