1

Sorry for the long question, I tried to keep it as standalone as possible.

For my master thesis I'm analyzing historical data coming from price oracles contract events. In particular, the MakerDAO medianizer contract receives regular price updates from 13 different feeds that sign the data before being sent to the oracle by a relay network (Maker oracle docs).

In order to identify the behavior of a single price feed over time I need to recover the related addresses from the signatures, since the relay network sorts them before sending them to the oracle to ease the median computation.

The medianizer smart contract itself also recovers those addresses in order to verify that they are authorized feeders. This is done in this way in solidity:

function recover(uint256 val_, uint256 age_, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        return ecrecover(
            keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(abi.encodePacked(val_, age_, wat)))),
            v, r, s
        );
    }

I tried to replicate that behavior in python, through eth_keys.py, eth_abi.py and web3.py libraries, but since they expect data in different format and encoding it does not work:

from eth_keys import KeyAPI
from eth_keys.backends import NativeECCBackend
from eth_abi.packed import encode_abi_packed
from web3 import Web3

key_api = KeyAPI(NativeECCBackend)

def recover_addr(val, age, v, r, s):
    if v == 27:
        v = b'\x00'
    elif v == 28:     
        v = b'\x01'
    else: 
        raise Exception("Wrong value for v")
    sig_bytes = b''.join([r, s, v])
    sig = KeyAPI.Signature(signature_bytes=sig_bytes)
    wat = "ETHUSD"
    message_hash = Web3.keccak(encode_abi_packed(['string', 'bytes'],
        ("\x19Ethereum Signed Message:\n32", 
        Web3.keccak(encode_abi_packed(['uint','uint','string'], (val, age, wat))))))
    return key_api.ecdsa_recover(message_hash, sig).to_address()

The values I have for the input parameters come from:

contract.decode_function_input(tx['input'])

where contract is defined as for this question, and the tx comes from etherscan.get_transactions_by_address.

An example output of the decode_function_input looks like this:

{
'val_': [231655080480000000000, 231673255320000000000, 231701702025000000000, 231722342740000000000, 231770000000000000000, 231929652920000000000, 231989882480000000000, 232024828000000000000, 232235000000000000000, 232385000000000000000, 232418825175000000000, 232588051380000000000, 232780000000000000000], 
'age_': [1583471768, 1583471768, 1583471770, 1583471795, 1583471775, 1583471777, 1583471779, 1583471816, 1583471829, 1583471900, 1583472090, 1583471914, 1583472166], 
'v': [28, 28, 27, 28, 28, 28, 28, 28, 27, 27, 27, 28, 27], 
'r': [b'\xfe\\_\x9f\xd1(\xc9\xf2\x07\xb6\xa7\xfb\xa2\xbf\xe3\x92\x9e\r\x84\x83\xcb\x83.z$&\x04\x84\xe0\x1e\xa8?', b"\xb8\x12\xec\xd4\x05&\x8d\x8e-\x92'`(U\xc3\xe9\xc9\xa6t2\xc6i\xfc5jI\x14\xba7+\xbb\x04", b'\x15\x8eQ\xc7jr%4\xf7\xf9,\x18G\xd6\xe6\xc6Sp\xf7\xc1\x82%sdJ\x87\xe9Ij\x9a\xa0\x1e', b'\xe0y\xc4\x8c\xcd~@\xd6\xd6\xea\x11\xb0\xaf\xe1\xa6k\xa7%$\xf43P\x05\t\xb3\xe1k\x18v\xe2\xd8\xee', b'\xc88=k\x0e\xd6\xa89\x83\xb4<\xa7|\xcez\x94\xf3p\x96\x13)!\x11\xb4\xa6g\x8b\x93\xe2Wz\xfa', b'\xfdyO\xe3c\x81\xaf<\xecd\xa8\x8a\x8e\x05L\x0f\xfd\xe5}b\xea{\xc6\x8e\xad\x98f\x07\xd9#\x8ei', b'+\x06\x8cu\xb0\xba;\xb1].!\xc4/a\x19[)\xfbtf\x8d\xe9\xd2\xa6\xe73"\x8c\xe5\xba\xc0\xb0', b'\xcf\xeb\xc8\x1c\x0f<\x86\xa5rD\xc4c\xbfF\x99O\x8aS\xcazH;g\xcb\xfd\xb0"C\xcb~\x93\xb5', b"\x02\x8a\x8c\xc5\xcb\xfb6\xeaZ\xc9/\xd2\x89I\xfa\xac\x9a\x19\x87\xb3\xf6\xee0\x16\x0e\x8e\xdc'\x1d\x94\xa5l", b'\xafe \x06r\xf3;\x8a\xc9T\x91\xf9\x9b\xd9)I\xd3.4\xb2\xbc\xcd\x87T\xc0\xafc\x10R\x9dC\xe6', b'\x00\x92\xb8\\\xfd\x16\xa0\xd0S)^u\xd8\xc2\x9bJ\xa7\x01=Ip\xc9f\x11E\xb888\x17\xe3(9', b'7\xdc\xd4\x92\xe0u\xc1\xb6\n@0\xdcl\x8dX\xe8\x93\x8b\xb1\xf6h\xc7\x87\x90\xdc\x83\xc0\x9f\x1c9\x97\xef', b'\x00\x8b\x96^\xc1@\x1b\xa2)DM,c\xf7y\xafO>Cv\xf11\x92K\xb4\xd0\xcbh5\xfb\xa5\xae'], 
's': [b'g\x138\xc8\t1S\xfe(\x053\x9c\xa62\xc6J\x94"\xa4\x0b\x96O\x00\x83danR\xe6\x94h\xd0', b'\x01\x8cj\xb6C\xa0\xb45c\x1f24>\x11\x1f\xfd\x81\xaf\x87\x1d>\xa0?,\xea\xb8B\xe7\x85\xcb\xd1\xd2', b'8d\xc5\xa9\\\x87D\x88\x88\xfdyu:y\x7f\xddC\xb87\xe3\xa6\x08\r!EA\xc8\xbeRG\xbc\xd2', b'}\xd3\xdb\xe8\xd7\x98\x96\xde\xab\x1e\xe2\x13l\x0e\xafRsU\xf7W\x92\x18?\x87\x07]\xfc\xd1?b\xf8\xb2', b'C\\\xe7\xcdY]\xf6\xeae&\xec\xa6TMx\x03\x89\x1c\xb1\r_\x06\xed\x1e\x87C\xd5\xe0\x0f\xa1<\xdc', b"\x14\xa5,\x02pS\x10\xddz)^\x9a\\2<\xed2'\nlw\x01\x13\xbcV\xae\t\x86\xc6.\xe8\xf4", b'\x0eu\xefZ\x0e\x0c\xc4\x10\xf42\x9e\xf8\x06\xb7\x90WH\xa4\xa8\x02\x8fX\xc4\xce\x15\xee\xd8zBv( ', b'N\xfa~\xc24k\xa9\xb2,\xdc\x8e\xbd7\x1c.L\xe1\xccO\x1e\x86\\\xf4\xb3\x96\xe3Vu[\x90\xff\xda', b'V\x91vR\x14\xd7ckc\x1d`f\x14\x0e^\xb2\x1f\xd7Y_5\x81\xeb\x1b\xe9\x06\xa4\xeb\x08?\xf4A', b'8\xfa\x14DJ\x0b\xe3\xb0\x03\x00\xec\xdc;\x1b\x16\xd3\x8f\x93\xf9\x85D\xc8\xc0<S\x10\xb1\xf9\xc9\x10#L', b'\x10\xb3me\xc8\xc4H\xba\x03A\x17\xbe\xe0A8Y\xe2\xb8\x084\xaa\x93\x90\xa2=P~62\xab\xfc(', b'\x08\x9f\xac\x03K\xf9\xd5\xbb\xd6\xd9~\xbe\xafA48\xbb2p\x042)(b\x8c\r\x1cg[\x11\x1f\x8b', b"a['\xe6\x1f\xcf\x06\xef\x84\xf1f\xf4\x13\xe4\xeaW\x11\xedt\x1a\xe5.BG,\xa0\xa2T\xb3\xc9\xa2\xac"]
}

So, everything executes without errors, but the addresses I get are meaningless, they don't exist on etherscan and never repeat over different txs as I would expect.

This might be caused by the fact that I arbitrarily made some choices, like the mapping for v and the input types to encode_abi_packed, and I have no clue whether any of them is correct while being unable to find documentation that can prove me right or wrong. Anybody has any hints?

  • You can try to set keccak256 output to bytes32, also do the same instead of string for wat type. Other option is to debug_traceTransactions to watch intermediate values. – Ismael Mar 7 at 18:52
  • 1
    Thanks, encode_abi_packed(['uint','uint','bytes32'], (val, age, bytes(wat, 'utf8')))) seems to solve the issue, or at least the resulting addresses repeat and appear on etherscan. – St3p Mar 8 at 18:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.