First, I have to clarify that you stated that the sha3 hash is the result of sha256(x1, y1, x2, y2), but then you tried to calculate sha256(x1, x2, y1, y2). I don't know which of those gave you the result, so I am going to show the solution to both tasks.
Precognition
- the usage of sha3(...) is equal to the usage of keccak256(...) in
Solidity. In reality, a keccak256 hash of the data is calculated.
- The usage of multiple arguments inside keccak256(...) is discouraged and will be deprecated in the future.
Cite (https://Solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html?highlight=abi.encodePacked#abi-encoding-functions):
Furthermore, keccak256(abi.encodePacked(a, b)) is a more explicit way to compute keccak256(a, b), which will be deprecated in future versions.
This note also leads to another realization. Since keccak256(a,b) is equal to keccak256(abi.encodePacked(a, b)), we have to find out how the abi.encodePacked(...) function operates to finally figure out which data was used for your sha256(x1,y1,x2,y2) call.
- abi.encodePacked(...) packs the arguments into one number.
Cite (https://Solidity.readthedocs.io/en/v0.4.24/abi-spec.html#non-standard-packed-mode):
Solidity supports a non-standard packed mode where:
no function selector is encoded,
types shorter than 32 bytes are neither zero padded nor sign extended and
dynamic types are encoded in-place and without the length.
As an example encoding int1, bytes1, uint16, string with values -1, 0x42, 0x2424, "Hello, world!" results in
0xff42242448656c6c6f2c20776f726c6421
^^ int1(-1)
^^ bytes1(0x42)
^^^^ uint16(0x2424)
^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field
More specifically, each statically-sized type takes as many bytes as its range has and dynamically-sized types like string, bytes or uint[] are encoded without their length field. This means that the encoding is ambiguous as soon as there are two dynamically-sized elements.
Solution using Solidity
After implementing the solution in Python, my results were not equal to your given hash. I was looking quite a long time for my error, but I could not find one. So I had to assume, that the hash you provided could actually not belong to the result of sha256(x1,x2,y1,y2) or sha256(x1,y1,x2,y2), which turned out to be the case.
You can execute the following code at https://remix.ethereum.org
pragma Solidity ^0.4.7;
import "remix_tests.sol"; // this import is automatically injected by Remix.
contract Test {
function testPackingAndHashing() public pure returns (bytes, bytes, bytes32, bytes32) {
address x1 = 0x0123456789012345678901234567890123456789;
address x2 = 0x0123456789012345678901234567890123456789;
uint256 y1 = 0x0000000000000000000000000000000000000000000000000000000000000001;
uint256 y2 = 0x0000000000000000000000000000000000000000000000000000000000000001;
return (abi.encodePacked(x1, x2, y1, y2), abi.encodePacked(x1, y1, x2, y2),
keccak256(abi.encodePacked(x1, x2, y1, y2)), keccak256(abi.encodePacked(x1, y1, x2, y2)));
}
}
Executing the code gives me the following results:
0: bytes: 0x0123456789012345678901234567890123456789012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
1: bytes: 0x0123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000000101234567890123456789012345678901234567890000000000000000000000000000000000000000000000000000000000000001
2: bytes32: 0x8507cc9ba3e98ea475462d270a7c3d1f516850eef73042db8942d7d171164e79
3: bytes32: 0xb11d6ebcb58892c42346a73b14a0d1422310158ce075b733ad1e23b86309cc8c
Solution using Python
The existing Python libraries for Ethereum can be found in the package pyethereum (https://github.com/ethereum/pyethereum). They use the package "pycryptodome" (https://pycryptodome.readthedocs.io/en/latest/src/introduction.html) for their keccak256 hash calculations, which I will use in the following example as well.
Python code showing how the get the correct hash for solidities sha256(x1,y1,x2,y2) and sha256(x1,x2,y1,y2) calls:
from Crypto.Hash import keccak
from math import ceil
kec1 = keccak.new(digest_bits=256)
kec2 = keccak.new(digest_bits=256)
x1 = "0123456789012345678901234567890123456789"
x2 = "0123456789012345678901234567890123456789"
y1 = "0000000000000000000000000000000000000000000000000000000000000001"
y2 = "0000000000000000000000000000000000000000000000000000000000000001"
c1 = x1 + x2 + y1 + y2
c2 = x1 + y1 + x2 + y2
c1_byte_count = ceil(len(c1)/2)
c2_byte_count = ceil(len(c2)/2) # same as c1_byte_count of course
# note that Ethereum uses big endian representation for lists of bytes
c1_bytes = int(c1, 16).to_bytes(c1_byte_count, "big")
c2_bytes = int(c2, 16).to_bytes(c2_byte_count, "big")
c1_result = kec1.update(c1_bytes).hexdigest()
c2_result = kec2.update(c2_bytes).hexdigest()
print("Result of old solidity sha3(x1,x2,y1,y2): ", c1_result)
print("Result of old solidity sha3(x1,y1,x2,y2): ", c2_result)
Output:
Result of old solidity sha3(x1,x2,y1,y2): 8507cc9ba3e98ea475462d270a7c3d1f516850eef73042db8942d7d171164e79
Result of old solidity sha3(x1,y1,x2,y2): b11d6ebcb58892c42346a73b14a0d1422310158ce075b733ad1e23b86309cc8c
Best Answer
I was wondering about this exact behaviour the other day but didn't have time to investigate it. The short answer is that Javascript itself doesn't allow for function overloading, so when the contract object is being built from the ABI, functions of the same name get overwritten.
...
The following 'homosignet' (?) contract contains three overloaded functions of the same name, the same JS argument type
typeof "0x123" == "string"
but with different ABI argument types,string
,bytes
,address
We can see in Remix that the three are unique ABI functions having unique function signatures of:
And that all three functions return the expected strings of "was string", "was bytes" and "was address".
However, if I hook the contract using web3.js and call the functions of the resulting JS object from the console, as in the following testcode and case:
The return values are all the same and in this case, all arguments are being parsed as type
string
, even the number literal.This makes sense because Javascript does not allow function overloading and when you look at the contract object, indeed only one
k.f()
exists.So it seems that calling overloaded functions requires a low level call with pre-encoded call object :
Which looks something like this when calling
f(bytes b)
. Then you need to decode the raw return data.