Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions native-to-erc20/contracts/signatures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Function signatures
executeSignatures(uint8[],bytes32[],bytes32[],bytes): 0x232a2c1d

submitSignature(bytes signature, bytes message): 0x630cea8e

submitSignatureOfMessageWithUnknownLength(bytes,bytes): 0x9b5a5489


# Event signatures

CollectedSignatures(address,bytes32,uint256): 0x41555740


done using: https://piyolab.github.io/playground/ethereum/getEncodedFunctionSignature/
74 changes: 74 additions & 0 deletions native-to-erc20/contracts/test/contracts/MessageWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
pragma solidity 0.4.24;

contract MessageWrapper {

function validatorRecover(
bytes _message,
uint8 _v,
bytes32 _r,
bytes32 _s
) public returns (address recoveredAddress) {
bytes32 hash = hashMessageOfUnknownLength(_message);
recoveredAddress = ecrecover(hash, _v, _r, _s);
}

function hashMessage(bytes message) public pure returns (bytes32) {
bytes memory prefix = "\x19Ethereum Signed Message:\n";
// message is always 84 length
string memory msgLength = "104";
return keccak256(abi.encodePacked(prefix, msgLength, message));
}

function hashMessageOfUnknownLength(bytes message) public pure returns (bytes32) {
bytes memory prefix = "\x19Ethereum Signed Message:\n";
uint256 lengthOffset;
uint256 length;
assembly {
// The first word of a string is its length
length := mload(message)
// The beginning of the base-10 message length in the prefix
lengthOffset := add(prefix, 57)
}
uint256 lengthLength = 0;
// The divisor to get the next left-most message length digit
uint256 divisor = 100000;
// Move one digit of the message length to the right at a time
while (divisor != 0) {
// The place value at the divisor
uint256 digit = length / divisor;
if (digit == 0) {
// Skip leading zeros
if (lengthLength == 0) {
divisor /= 10;
continue;
}
}
// Found a non-zero digit or non-leading zero digit
lengthLength++;
// Remove this digit from the message length's current value
length -= digit * divisor;
// Shift our base-10 divisor over
divisor /= 10;
// Convert the digit to its ASCII representation (man ascii)
digit += 0x30;
// Move to the next character and write the digit
lengthOffset++;
assembly {
mstore8(lengthOffset, digit)
}
}
// The null string requires exactly 1 zero (unskip 1 leading 0)
if (lengthLength == 0) {
lengthLength = 1 + 0x19 + 1;
} else {
lengthLength += 1 + 0x19;
}
// Truncate the tailing zeros from the prefix
assembly {
mstore(prefix, lengthLength)
}
return keccak256(prefix, message);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const { HttpListProviderError } = require('http-list-provider')
const Web3 = require('web3')
const { signatureToVRS } = require('../../utils/message')
const estimateGas = require('./estimateGas')
const {
AlreadyProcessedError,
IncompatibleContractError,
InvalidValidatorError
} = require('../../utils/errors')

const web3 = new Web3()

const createRawTx = async ({ homeBridge, foreignBridge, logger, colSignature, foreignValidatorContract }) => {
const { messageHash, NumberOfCollectedSignatures } = colSignature.returnValues

logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
const message = await homeBridge.methods.message(messageHash).call()
const expectedMessageLength = await homeBridge.methods.requiredMessageLength().call()

const requiredSignatures = []
requiredSignatures.length = NumberOfCollectedSignatures
requiredSignatures.fill(0)

const [v, r, s] = [[], [], []]
logger.debug('Getting message signatures')
const signaturePromises = requiredSignatures.map(async (el, index) => {
logger.debug({ index }, 'Getting message signature')
const signature = await homeBridge.methods.signature(messageHash, index).call()
const recover = signatureToVRS(signature)
v.push(recover.v)
r.push(recover.r)
s.push(recover.s)
})
// let index = 0
// debugger
// console.log({ NumberOfCollectedSignatures })

await Promise.all(signaturePromises)

let gasEstimate, methodName
try {
logger.debug('Estimate gas')
const result = await estimateGas({
foreignBridge,
validatorContract: foreignValidatorContract,
v,
r,
s,
message,
numberOfCollectedSignatures: NumberOfCollectedSignatures,
expectedMessageLength
})
logger.info({ result }, 'Gas estimated')
gasEstimate = result.gasEstimate
methodName = result.methodName
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
} else if (e instanceof AlreadyProcessedError) {
logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`)
return
} else if (
e instanceof IncompatibleContractError ||
e instanceof InvalidValidatorError
) {
logger.error(`The message couldn't be processed; skipping: ${e.message}`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await foreignBridge.methods[methodName](v, r, s, message).encodeABI()
return {
data,
gasEstimate,
transactionReference: colSignature.transactionHash,
to: foreignBridge.options.address
}
}

module.exports = createRawTx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ async function estimateGas ({
expectedMessageLength
}) {
try {
debugger
let gasEstimate, methodName
if (message && message.length !== 2 + 2 * expectedMessageLength) { /* see ../../utils/message.js#createMessage */
logger.debug('foreignBridge.methods.executeNewSetSignatures')
console.log({
v, r, s, message
})
// gasEstimate = 3000000
gasEstimate = await foreignBridge.methods
.executeNewSetSignatures(v, r, s, message)
.estimateGas()
Expand Down Expand Up @@ -63,6 +68,7 @@ async function estimateGas ({
}

// check if all the signatures were made by validators
const validators = {}
for (let i = 0; i < v.length; i++) {
const address = web3.eth.accounts.recover(message, web3.utils.toHex(v[i]), r[i], s[i])
logger.debug({ address }, 'Check that signature is from a validator')
Expand All @@ -71,6 +77,11 @@ async function estimateGas ({
if (!isValidator) {
throw new InvalidValidatorError(`Message signed by ${address} that is not a validator`)
}
if (validators[address]) {
logger.error('validator signed twice', { address })
throw new Error('Validator signed twice')
}
validators[address] = true
}

logger.error(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function processInitiateChangeBuilder (config) {
const foreignBridgeVersion = await foreignBridgeStorage.methods.version().call()
const message = createNewSetMessage({
foreignBridgeVersion,
newSet: newSet,
newSet: [...newSet],
transactionHash: initiateChange.transactionHash,
blockNumber,
bridgeAddress: foreignBridgeAddress
Expand Down
Loading