Skip to content

Commit

Permalink
contracts: simplify unpacking code for large gas saving
Browse files Browse the repository at this point in the history
  • Loading branch information
dtebbs committed Dec 19, 2019
1 parent 043164b commit 394722f
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 75 deletions.
84 changes: 10 additions & 74 deletions zeth-contracts/contracts/Bytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,81 +8,16 @@ library Bytes {

function sha256_digest_from_field_elements(uint input0, uint input1)
internal pure returns (bytes32) {

// We know that input[0] is a field element. Thus, it is encoded on 253
// bits, and it should be the biggest between inputs[0] and inputs[1].
// Inputs[0] actually contains 253 bits from the digest
bytes32 inverted_input1 = flip_endianness_bytes32(bytes32(input0));

// As opposed to inputs[0], inputs[1] is encoded on 253 bits (because it
// is a field element) but contains ONLY 3 bits from the digest (it is a
// super small number in the set [0, ..., 7]). BECAUSE we know that
// inputs[0] has only 253 bits from the digest and that inputs[1] has
// only 3 and because we know that both are represented as bytes32, we
// know that the last 3 bits of the inputs[0] are going to be zeroes (we
// want to represent a 253-bit encoded element as a 256-bit digest). In
// the same way, we know that in the bytes32 representation of inputs[1]
// we will have 253 zero bits and only 3 meaningful bits (note that 3
// bits is not sufficient to be represented on half a bit, so we need to
// take this into consideration when we reverse the endianness and when
// we shift the bits). We reverse the endianness of the whole inputs[0]
// (the entire 253-bit string inputs[0]) contains information on the
// field
bytes1 last_byte_prefix = get_last_byte(inverted_input1);

// We only reverse the last byte of the input[1] because we know that
// input[1] is a very small number only represented on 3 field bits, so
// the meaningful data we want to manipulate is only on the last bytes
// of this bytes32 input
bytes1 last_byte_suffix = get_last_byte(bytes32(input1));

// After selecting the last byte of input[1], we inverse only this byte
// and we shift 5 times because we have somehting like: 0x4 initally,
// which is in reality 0x000...004 Thus the last byte is 0x04 --> 0000
// 0100 (in binary). Only the last `100` bits represent meaningful data
// (the first 5 bit of value '0' are just here to fill the space in the
// byte), so when we reverse the byte, we have:
//
// 0010 0000 --> But again only 3 bits are meaningful in this
// case. This time the 5 last bits are padding,
// The 32-byte digest is encoded as:
// - input0: a field element containing the low order 253 bits
// - input1: a field element containing the high order 3 bits
//
// Thus we push the meaningful data to the right. Now we have something
// like: `0000 0001`. And we know that the last 3 bits of input[0] are
// '0' bits that have been padded to create a byte32 out of a 253 bit
// string. Thus now, we have the last byte of input[0] being something
// like XXXX X000 (where X represent meangful bits). And the last byte
// of input[1] (the only meaningul byte of this input) being in the
// form: 0000 0YYY (where Y represent meaningful data). The only thing
// we need to do to recompose the digest our of our 2 field elements, is
// to XOR the last bytes of each reverse input.
uint8 n = 5;
// Converting bytes1 into 8 bit integer.
uint8 aInt = uint8(last_byte_suffix);
uint8 reversed = uint8(reverse_byte(aInt));

// Note, we store the result of 2 ** n in uint8. However, if n is to big
// (ie: n > 8). This can overflow. Here this is fine as n is NOT a user
// input, and does not aim to be changed. Nevertheless we need to keep
// this in mind. As a consequence we add the "dummy" require below that
// should fail if we manually change the value of n to be > 8.
require(
n < 8,
"The number of right shifts should be inferior to 8"
);
uint8 shifted = reversed / 2 ** n;
bytes1 shifted_byte = bytes1(shifted);

bytes1 res = last_byte_prefix ^ shifted_byte;

bytes memory bytes_digest = new bytes(32);
for (uint i = 0; i < 31; i++) {
bytes_digest[i] = inverted_input1[i];
}

bytes_digest[31] = res;
bytes32 sha256_digest = bytes_to_bytes32(bytes_digest, 0);
// Note that the bits are also packed in reverse order, so the
// reassembled value must be bit-reversed.

return (sha256_digest);
bytes32 recombined_reversed = bytes32(input0 + (input1 << 253));
bytes32 recombined = flip_bit_endianness_bytes32(recombined_reversed);
return recombined;
}

function bytes_to_bytes32(bytes memory b, uint offset)
Expand All @@ -95,7 +30,8 @@ library Bytes {
return out;
}

function flip_endianness_bytes32(bytes32 a) internal pure returns(bytes32) {
function flip_bit_endianness_bytes32(bytes32 a)
internal pure returns(bytes32) {
uint r;
uint b;
for (uint i = 0; i < 32; i++) {
Expand Down
2 changes: 1 addition & 1 deletion zeth-contracts/contracts/Bytes_tests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ contract Bytes_tests {
function testFlipEndiannessBytes32() public pure returns (bool) {
// solium-disable-next-line
bytes32 test_bytes = 0x00000000000000000000000000000000000000000000000000000000000000AF;
bytes32 reversed_bytes = Bytes.flip_endianness_bytes32(test_bytes);
bytes32 reversed_bytes = Bytes.flip_bit_endianness_bytes32(test_bytes);

// solium-disable-next-line
bool ok = (reversed_bytes == 0xF500000000000000000000000000000000000000000000000000000000000000);
Expand Down

0 comments on commit 394722f

Please sign in to comment.