diff --git a/contracts/StatefulSponge.sol b/contracts/StatefulSponge.sol index 083effa..57cd35f 100644 --- a/contracts/StatefulSponge.sol +++ b/contracts/StatefulSponge.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.23; +pragma solidity 0.8.15; import { Lib_Keccak256 } from "contracts/lib/LK.sol"; diff --git a/contracts/lib/LK.sol b/contracts/lib/LK.sol index 2747d68..b56bdb7 100644 --- a/contracts/lib/LK.sol +++ b/contracts/lib/LK.sol @@ -5,6 +5,19 @@ pragma solidity >0.5.0; // https://github.com/firefly/wallet/blob/master/source/libs/ethers/src/keccak256.c library Lib_Keccak256 { + /// @notice The round constants for the keccak256 hash function. Packed in memory for efficient reading during the + /// permutation. + bytes private constant ROUND_CONSTANTS = abi.encode( + 0x00000000000000010000000000008082800000000000808a8000000080008000, // r1,r2,r3,r4 + 0x000000000000808b000000008000000180000000800080818000000000008009, // r5,r6,r7,r8 + 0x000000000000008a00000000000000880000000080008009000000008000000a, // r9,r10,r11,r12 + 0x000000008000808b800000000000008b80000000000080898000000000008003, // r13,r14,r15,r16 + 0x80000000000080028000000000000080000000000000800a800000008000000a, // r17,r18,r19,r20 + 0x8000000080008081800000000000808000000000800000018000000080008008 // r21,r22,r23,r24 + ); + + uint64 private constant U64_MASK = 0xFFFFFFFFFFFFFFFF; + struct CTX { uint64[25] A; } @@ -34,52 +47,70 @@ library Lib_Keccak256 { uint64 D3 = (C4 << 1) ^ (C4 >> 63) ^ C2; uint64 D4 = (C0 << 1) ^ (C0 >> 63) ^ C3; c.A[0] ^= D0; - uint64 A1 = ((c.A[1] ^ D1) << 1) ^ ((c.A[1] ^ D1) >> (64 - 1)); // + uint64 A1 = ((c.A[1] ^ D1) << 1) ^ ((c.A[1] ^ D1) >> (64 - 1)); c.A[1] = ((c.A[6] ^ D1) << 44) ^ ((c.A[6] ^ D1) >> (64 - 44)); - // 6, 1, 44, D1 - c.A[6] = ((c.A[9] ^ D4) << 20) ^ ((c.A[9] ^ D4) >> (64 - 20)); // 9, 6, 20, D4 - c.A[9] = ((c.A[22] ^ D2) << 61) ^ ((c.A[22] ^ D2) >> (64 - 61)); // 22, 9, 61, D2 - c.A[22] = ((c.A[14] ^ D4) << 39) ^ ((c.A[14] ^ D4) >> (64 - 39)); // 14, 22, 39, D4 - c.A[14] = ((c.A[20] ^ D0) << 18) ^ ((c.A[20] ^ D0) >> (64 - 18)); // 20, 14, 18, D0 - c.A[20] = ((c.A[2] ^ D2) << 62) ^ ((c.A[2] ^ D2) >> (64 - 62)); // 2, 20, 62, D2 - c.A[2] = ((c.A[12] ^ D2) << 43) ^ ((c.A[12] ^ D2) >> (64 - 43)); // 12, 2, 43, D2 - c.A[12] = ((c.A[13] ^ D3) << 25) ^ ((c.A[13] ^ D3) >> (64 - 25)); // 13, 12, 25, D3 - c.A[13] = ((c.A[19] ^ D4) << 8) ^ ((c.A[19] ^ D4) >> (64 - 8)); // 19, 13, 8, D4 - c.A[19] = ((c.A[23] ^ D3) << 56) ^ ((c.A[23] ^ D3) >> (64 - 56)); // 23, 19, 56, D3 - c.A[23] = ((c.A[15] ^ D0) << 41) ^ ((c.A[15] ^ D0) >> (64 - 41)); // 15, 23, 41, D0 - c.A[15] = ((c.A[4] ^ D4) << 27) ^ ((c.A[4] ^ D4) >> (64 - 27)); // 4, 15, 27, D4 - c.A[4] = ((c.A[24] ^ D4) << 14) ^ ((c.A[24] ^ D4) >> (64 - 14)); // 24, 4, 14, D4 - c.A[24] = ((c.A[21] ^ D1) << 2) ^ ((c.A[21] ^ D1) >> (64 - 2)); // 21, 24, 2, D1 - c.A[21] = ((c.A[8] ^ D3) << 55) ^ ((c.A[8] ^ D3) >> (64 - 55)); // 8, 21, 55, D3 - c.A[8] = ((c.A[16] ^ D1) << 45) ^ ((c.A[16] ^ D1) >> (64 - 45)); // 16, 8, 45, D1 - c.A[16] = ((c.A[5] ^ D0) << 36) ^ ((c.A[5] ^ D0) >> (64 - 36)); // 5, 16, 36, D0 - c.A[5] = ((c.A[3] ^ D3) << 28) ^ ((c.A[3] ^ D3) >> (64 - 28)); // 3, 5, 28, D3 - c.A[3] = ((c.A[18] ^ D3) << 21) ^ ((c.A[18] ^ D3) >> (64 - 21)); // 18, 3, 21, D3 - c.A[18] = ((c.A[17] ^ D2) << 15) ^ ((c.A[17] ^ D2) >> (64 - 15)); // 17, 18, 15, D2 - c.A[17] = ((c.A[11] ^ D1) << 10) ^ ((c.A[11] ^ D1) >> (64 - 10)); // 11, 17, 10, D1 - c.A[11] = ((c.A[7] ^ D2) << 6) ^ ((c.A[7] ^ D2) >> (64 - 6)); // 7, 11, 6, D2 - c.A[7] = ((c.A[10] ^ D0) << 3) ^ ((c.A[10] ^ D0) >> (64 - 3)); // 10, 7, 3, D0 + c.A[6] = ((c.A[9] ^ D4) << 20) ^ ((c.A[9] ^ D4) >> (64 - 20)); + c.A[9] = ((c.A[22] ^ D2) << 61) ^ ((c.A[22] ^ D2) >> (64 - 61)); + c.A[22] = ((c.A[14] ^ D4) << 39) ^ ((c.A[14] ^ D4) >> (64 - 39)); + c.A[14] = ((c.A[20] ^ D0) << 18) ^ ((c.A[20] ^ D0) >> (64 - 18)); + c.A[20] = ((c.A[2] ^ D2) << 62) ^ ((c.A[2] ^ D2) >> (64 - 62)); + c.A[2] = ((c.A[12] ^ D2) << 43) ^ ((c.A[12] ^ D2) >> (64 - 43)); + c.A[12] = ((c.A[13] ^ D3) << 25) ^ ((c.A[13] ^ D3) >> (64 - 25)); + c.A[13] = ((c.A[19] ^ D4) << 8) ^ ((c.A[19] ^ D4) >> (64 - 8)); + c.A[19] = ((c.A[23] ^ D3) << 56) ^ ((c.A[23] ^ D3) >> (64 - 56)); + c.A[23] = ((c.A[15] ^ D0) << 41) ^ ((c.A[15] ^ D0) >> (64 - 41)); + c.A[15] = ((c.A[4] ^ D4) << 27) ^ ((c.A[4] ^ D4) >> (64 - 27)); + c.A[4] = ((c.A[24] ^ D4) << 14) ^ ((c.A[24] ^ D4) >> (64 - 14)); + c.A[24] = ((c.A[21] ^ D1) << 2) ^ ((c.A[21] ^ D1) >> (64 - 2)); + c.A[21] = ((c.A[8] ^ D3) << 55) ^ ((c.A[8] ^ D3) >> (64 - 55)); + c.A[8] = ((c.A[16] ^ D1) << 45) ^ ((c.A[16] ^ D1) >> (64 - 45)); + c.A[16] = ((c.A[5] ^ D0) << 36) ^ ((c.A[5] ^ D0) >> (64 - 36)); + c.A[5] = ((c.A[3] ^ D3) << 28) ^ ((c.A[3] ^ D3) >> (64 - 28)); + c.A[3] = ((c.A[18] ^ D3) << 21) ^ ((c.A[18] ^ D3) >> (64 - 21)); + c.A[18] = ((c.A[17] ^ D2) << 15) ^ ((c.A[17] ^ D2) >> (64 - 15)); + c.A[17] = ((c.A[11] ^ D1) << 10) ^ ((c.A[11] ^ D1) >> (64 - 10)); + c.A[11] = ((c.A[7] ^ D2) << 6) ^ ((c.A[7] ^ D2) >> (64 - 6)); + c.A[7] = ((c.A[10] ^ D0) << 3) ^ ((c.A[10] ^ D0) >> (64 - 3)); c.A[10] = A1; } function keccak_chi(CTX memory c) internal pure { - uint256 i; - uint64 A0; - uint64 A1; - uint64 A2; - uint64 A3; - uint64 A4; - for (i = 0; i < 25; i += 5) { - A0 = c.A[0 + i]; - A1 = c.A[1 + i]; - A2 = c.A[2 + i]; - A3 = c.A[3 + i]; - A4 = c.A[4 + i]; - c.A[0 + i] ^= ~A1 & A2; - c.A[1 + i] ^= ~A2 & A3; - c.A[2 + i] ^= ~A3 & A4; - c.A[3 + i] ^= ~A4 & A0; - c.A[4 + i] ^= ~A0 & A1; + assembly { + // fetch a state element from the passed `StateMatrix` struct memory ptr. + function stateElem(ptr, idx) -> elem { + elem := mload(add(ptr, shl(0x05, idx))) + } + + // set a state element in the passed `StateMatrix` struct memory ptr. + function setStateElem(ptr, idx, data) { + mstore(add(ptr, shl(0x05, idx)), and(data, U64_MASK)) + } + + // Inner `chi` function, unrolled in `chi` for performance. + function innerChi(ptr, start) { + let A0 := stateElem(ptr, start) + let A1 := stateElem(ptr, add(start, 1)) + let A2 := stateElem(ptr, add(start, 2)) + let A3 := stateElem(ptr, add(start, 3)) + let A4 := stateElem(ptr, add(start, 4)) + + setStateElem(ptr, start, xor(A0, and(not(A1), A2))) + setStateElem(ptr, add(start, 1), xor(A1, and(not(A2), A3))) + setStateElem(ptr, add(start, 2), xor(A2, and(not(A3), A4))) + setStateElem(ptr, add(start, 3), xor(A3, and(not(A4), A0))) + setStateElem(ptr, add(start, 4), xor(A4, and(not(A0), A1))) + } + + // Performs the `chi` step of the Keccak-f[1600] permutation on the passed `StateMatrix` struct memory ptr + function chi(ptr) { + innerChi(ptr, 0) + innerChi(ptr, 5) + innerChi(ptr, 10) + innerChi(ptr, 15) + innerChi(ptr, 20) + } + + chi(add(c, 0x20)) } } @@ -92,15 +123,66 @@ library Lib_Keccak256 { } function sha3_xor_input(CTX memory c, bytes memory dat) internal pure { - for (uint256 i = 0; i < 17; i++) { - uint256 bo = i * 8; - c.A[i] ^= uint64(uint8(dat[bo + 7])) << 56 | uint64(uint8(dat[bo + 6])) << 48 - | uint64(uint8(dat[bo + 5])) << 40 | uint64(uint8(dat[bo + 4])) << 32 | uint64(uint8(dat[bo + 3])) << 24 - | uint64(uint8(dat[bo + 2])) << 16 | uint64(uint8(dat[bo + 1])) << 8 | uint64(uint8(dat[bo + 0])) << 0; + assembly { + // The input must be 1088 bits long. + if iszero(eq(mload(dat), 136)) { revert(0, 0) } + + let dataPtr := add(dat, 0x20) + let statePtr := add(c, 0x20) + + // set a state element in the passed `StateMatrix` struct memory ptr. + function setStateElem(ptr, idx, data) { + mstore(add(ptr, shl(0x05, idx)), and(data, U64_MASK)) + } + + // fetch a state element from the passed `StateMatrix` struct memory ptr. + function stateElem(ptr, idx) -> elem { + elem := mload(add(ptr, shl(0x05, idx))) + } + + // Inner sha3 absorb XOR function + function absorbInner(stateMatrixPtr, inputPtr, idx) { + let bo := shl(3, idx) + let boWord := mload(add(inputPtr, bo)) + + let res := + or( + or( + or(shl(56, byte(7, boWord)), shl(48, byte(6, boWord))), + or(shl(40, byte(5, boWord)), shl(32, byte(4, boWord))) + ), + or( + or(shl(24, byte(3, boWord)), shl(16, byte(2, boWord))), + or(shl(8, byte(1, boWord)), byte(0, boWord)) + ) + ) + setStateElem(stateMatrixPtr, idx, xor(stateElem(stateMatrixPtr, idx), res)) + } + + // Unroll the input XOR loop. + absorbInner(statePtr, dataPtr, 0) + absorbInner(statePtr, dataPtr, 1) + absorbInner(statePtr, dataPtr, 2) + absorbInner(statePtr, dataPtr, 3) + absorbInner(statePtr, dataPtr, 4) + absorbInner(statePtr, dataPtr, 5) + absorbInner(statePtr, dataPtr, 6) + absorbInner(statePtr, dataPtr, 7) + absorbInner(statePtr, dataPtr, 8) + absorbInner(statePtr, dataPtr, 9) + absorbInner(statePtr, dataPtr, 10) + absorbInner(statePtr, dataPtr, 11) + absorbInner(statePtr, dataPtr, 12) + absorbInner(statePtr, dataPtr, 13) + absorbInner(statePtr, dataPtr, 14) + absorbInner(statePtr, dataPtr, 15) + absorbInner(statePtr, dataPtr, 16) } } function sha3_permutation(CTX memory c) internal pure { + bytes memory rounds = ROUND_CONSTANTS; + uint256 round; for (round = 0; round < 24; round++) { keccak_theta_rho_pi(c); @@ -116,7 +198,21 @@ library Lib_Keccak256 { // } // } // keccak_iota - c.A[0] ^= get_round_constant(round); + assembly { + // set a state element in the passed `StateMatrix` struct memory ptr. + function setStateElem(ptr, idx, data) { + mstore(add(ptr, shl(0x05, idx)), and(data, U64_MASK)) + } + + // fetch a state element from the passed `StateMatrix` struct memory ptr. + function stateElem(ptr, idx) -> elem { + elem := mload(add(ptr, shl(0x05, idx))) + } + + let ptr := add(c, 0x20) + let roundConst := shr(192, mload(add(add(rounds, 0x20), shl(0x03, round)))) + setStateElem(ptr, 0, xor(stateElem(ptr, 0), roundConst)) + } } } @@ -127,10 +223,28 @@ library Lib_Keccak256 { return (val << 32) | (val >> 32); } - function get_hash(CTX memory c) internal pure returns (bytes32) { - return bytes32( - (uint256(flip(c.A[0])) << 192) | (uint256(flip(c.A[1])) << 128) | (uint256(flip(c.A[2])) << 64) - | (uint256(flip(c.A[3])) << 0) - ); + function get_hash(CTX memory c) internal pure returns (bytes32 hash_) { + assembly { + // convert a big endian 64-bit value to a little endian 64-bit value. + function toLE(beVal) -> leVal { + beVal := + or(and(and(shl(8, beVal), U64_MASK), 0xFF00FF00FF00FF00), and(shr(8, beVal), 0x00FF00FF00FF00FF)) + beVal := + or(and(and(shl(16, beVal), U64_MASK), 0xFFFF0000FFFF0000), and(shr(16, beVal), 0x0000FFFF0000FFFF)) + leVal := or(and(shl(32, beVal), U64_MASK), shr(32, beVal)) + } + + // fetch a state element from the passed `StateMatrix` struct memory ptr. + function stateElem(ptr, idx) -> elem { + elem := mload(add(ptr, shl(0x05, idx))) + } + + let stateMatrixPtr := add(c, 0x20) + hash_ := + or( + or(shl(192, toLE(stateElem(stateMatrixPtr, 0))), shl(128, toLE(stateElem(stateMatrixPtr, 1)))), + or(shl(64, toLE(stateElem(stateMatrixPtr, 2))), toLE(stateElem(stateMatrixPtr, 3))) + ) + } } } diff --git a/contracts/lib/LibKeccak.sol b/contracts/lib/LibKeccak.sol index 1e21a8b..c8fad3f 100644 --- a/contracts/lib/LibKeccak.sol +++ b/contracts/lib/LibKeccak.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.23; +pragma solidity 0.8.15; /// @title LibKeccak /// @notice An EVM implementation of the Keccak-f[1600] permutation. @@ -58,10 +58,10 @@ library LibKeccak { } // Performs an indivudual rho + pi computation, to be used in the full `thetaRhoPi` chain. - function rhoPi(ptr, src, dest, fact, dt) { - let xs1 := xor(stateElem(ptr, src), dt) + function rhoPi(ptr, destIdx, srcIdx, fact, dt) { + let xs1 := xor(stateElem(ptr, srcIdx), dt) let res := xor(shl64(fact, xs1), shr(sub(64, fact), xs1)) - setStateElem(ptr, dest, res) + setStateElem(ptr, destIdx, res) } // Performs the `theta`, `rho`, and `pi` steps of the Keccak-f[1600] permutation on @@ -84,29 +84,29 @@ library LibKeccak { let A1 := xor(shl64(1, xs1), shr(63, xs1)) setStateElem(ptr, 0, xor(stateElem(ptr, 0), D0)) - rhoPi(ptr, 6, 1, 44, D1) // 6, 1, 44, D1 - rhoPi(ptr, 9, 6, 20, D4) // 9, 6, 20, D4 - rhoPi(ptr, 22, 9, 61, D2) // 22, 9, 61, D2 - rhoPi(ptr, 14, 22, 39, D4) // 14, 22, 39, D4 - rhoPi(ptr, 20, 14, 18, D0) // 20, 14, 18, D0 - rhoPi(ptr, 2, 20, 62, D2) // 2, 20, 62, D2 - rhoPi(ptr, 12, 2, 43, D2) // 12, 2, 43, D2 - rhoPi(ptr, 13, 12, 25, D3) // 13, 12, 25, D3 - rhoPi(ptr, 19, 13, 8, D4) // 19, 13, 8, D4 - rhoPi(ptr, 23, 19, 56, D3) // 23, 19, 56, D3 - rhoPi(ptr, 15, 23, 41, D0) // 15, 23, 41, D0 - rhoPi(ptr, 4, 15, 27, D4) // 4, 15, 27, D4 - rhoPi(ptr, 24, 4, 14, D4) // 24, 4, 14, D4 - rhoPi(ptr, 21, 24, 2, D1) // 21, 24, 2, D1 - rhoPi(ptr, 8, 21, 55, D3) // 8, 21, 55, D3 - rhoPi(ptr, 16, 8, 45, D1) // 16, 8, 45, D1 - rhoPi(ptr, 5, 16, 36, D0) // 5, 16, 36, D0 - rhoPi(ptr, 3, 5, 28, D3) // 3, 5, 28, D3 - rhoPi(ptr, 18, 3, 21, D3) // 18, 3, 21, D3 - rhoPi(ptr, 17, 18, 15, D2) // 17, 18, 15, D2 - rhoPi(ptr, 11, 17, 10, D1) // 11, 17, 10, D1 - rhoPi(ptr, 7, 11, 6, D2) // 7, 11, 6, D2 - rhoPi(ptr, 10, 7, 3, D0) // 10, 7, 3, D0 + rhoPi(ptr, 1, 6, 44, D1) + rhoPi(ptr, 6, 9, 20, D4) + rhoPi(ptr, 9, 22, 61, D2) + rhoPi(ptr, 22, 14, 39, D4) + rhoPi(ptr, 14, 20, 18, D0) + rhoPi(ptr, 20, 2, 62, D2) + rhoPi(ptr, 2, 12, 43, D2) + rhoPi(ptr, 12, 13, 25, D3) + rhoPi(ptr, 13, 19, 8, D4) + rhoPi(ptr, 19, 23, 56, D3) + rhoPi(ptr, 23, 15, 41, D0) + rhoPi(ptr, 15, 4, 27, D4) + rhoPi(ptr, 4, 24, 14, D4) + rhoPi(ptr, 24, 21, 2, D1) + rhoPi(ptr, 21, 8, 55, D3) + rhoPi(ptr, 8, 16, 45, D1) + rhoPi(ptr, 16, 5, 36, D0) + rhoPi(ptr, 5, 3, 28, D3) + rhoPi(ptr, 3, 18, 21, D3) + rhoPi(ptr, 18, 17, 15, D2) + rhoPi(ptr, 17, 11, 10, D1) + rhoPi(ptr, 11, 7, 6, D2) + rhoPi(ptr, 7, 10, 3, D0) setStateElem(ptr, 10, A1) } @@ -234,11 +234,16 @@ library LibKeccak { /// @notice Squeezes the final keccak256 digest from the passed `StateMatrix`. function squeeze(StateMatrix memory _stateMatrix) internal pure returns (bytes32 hash_) { assembly { + // 64 bit logical shift + function shl64(a, b) -> val { + val := and(shl(a, b), U64_MASK) + } + // convert a big endian 64-bit value to a little endian 64-bit value. function toLE(beVal) -> leVal { - beVal := or(and(shl(8, beVal), 0xFF00FF00FF00FF00), and(shr(8, beVal), 0x00FF00FF00FF00FF)) - beVal := or(and(shl(16, beVal), 0xFFFF0000FFFF0000), and(shr(16, beVal), 0x0000FFFF0000FFFF)) - leVal := or(shl(32, beVal), shr(32, beVal)) + beVal := or(and(shl64(8, beVal), 0xFF00FF00FF00FF00), and(shr(8, beVal), 0x00FF00FF00FF00FF)) + beVal := or(and(shl64(16, beVal), 0xFFFF0000FFFF0000), and(shr(16, beVal), 0x0000FFFF0000FFFF)) + leVal := or(shl64(32, beVal), shr(32, beVal)) } // fetch a state element from the passed `StateMatrix` struct memory ptr. diff --git a/test/LibKeccak.t.sol b/test/LibKeccak.t.sol index c427e6b..4568e69 100644 --- a/test/LibKeccak.t.sol +++ b/test/LibKeccak.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.23; +pragma solidity 0.8.15; import { Test, console2 as console } from "forge-std/Test.sol";