diff --git a/lib/sha512/LibBytes.sol b/lib/sha512/LibBytes.sol new file mode 100644 index 0000000..4d30400 --- /dev/null +++ b/lib/sha512/LibBytes.sol @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: MIT +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// solhint-disable max-line-length +// Original library copied from +// https://github.com/0xProject/exchange-v3/blob/aae46bef841bfd1cc31028f41793db4fe7197084/contracts/utils/contracts/src/LibBytes.sol +// solhint-enable max-line-length + +pragma solidity ^0.8.30; + +library LibBytes { + using LibBytes for bytes; + + /// @dev Gets the memory address for a byte array. + /// @param input Byte array to lookup. + /// @return memoryAddress Memory address of byte array. This + /// points to the header of the byte array which contains + /// the length. + function rawAddress(bytes memory input) internal pure returns (uint256 memoryAddress) { + assembly { + memoryAddress := input + } + return memoryAddress; + } + + /// @dev Gets the memory address for the contents of a byte array. + /// @param input Byte array to lookup. + /// @return memoryAddress Memory address of the contents of the byte array. + function contentAddress(bytes memory input) internal pure returns (uint256 memoryAddress) { + assembly { + memoryAddress := add(input, 32) + } + return memoryAddress; + } + + /// @dev Copies `length` bytes from memory location `source` to `dest`. + /// @param dest memory address to copy bytes to. + /// @param source memory address to copy bytes from. + /// @param length number of bytes to copy. + function memCopy(uint256 dest, uint256 source, uint256 length) internal pure { + if (length < 32) { + // Handle a partial word by reading destination and masking + // off the bits we are interested in. + // This correctly handles overlap, zero lengths and source == dest + assembly { + let mask := sub(exp(256, sub(32, length)), 1) + let s := and(mload(source), not(mask)) + let d := and(mload(dest), mask) + mstore(dest, or(s, d)) + } + } else { + // Skip the O(length) loop when source == dest. + if (source == dest) { + return; + } + + // For large copies we copy whole words at a time. The final + // word is aligned to the end of the range (instead of after the + // previous) to handle partial words. So a copy will look like this: + // + // #### + // #### + // #### + // #### + // + // We handle overlap in the source and destination range by + // changing the copying direction. This prevents us from + // overwriting parts of source that we still need to copy. + // + // This correctly handles source == dest + // + if (source > dest) { + assembly { + // We subtract 32 from `sEnd` and `dEnd` because it + // is easier to compare with in the loop, and these + // are also the addresses we need for copying the + // last bytes. + length := sub(length, 32) + let sEnd := add(source, length) + let dEnd := add(dest, length) + + // Remember the last 32 bytes of source + // This needs to be done here and not after the loop + // because we may have overwritten the last bytes in + // source already due to overlap. + let last := mload(sEnd) + + // Copy whole words front to back + // Note: the first check is always true, + // this could have been a do-while loop. + // solhint-disable-next-line no-empty-blocks + for { + + } lt(source, sEnd) { + + } { + mstore(dest, mload(source)) + source := add(source, 32) + dest := add(dest, 32) + } + + // Write the last 32 bytes + mstore(dEnd, last) + } + } else { + assembly { + // We subtract 32 from `sEnd` and `dEnd` because those + // are the starting points when copying a word at the end. + length := sub(length, 32) + let sEnd := add(source, length) + let dEnd := add(dest, length) + + // Remember the first 32 bytes of source + // This needs to be done here and not after the loop + // because we may have overwritten the first bytes in + // source already due to overlap. + let first := mload(source) + + // Copy whole words back to front + // We use a signed comparisson here to allow dEnd to become + // negative (happens when source and dest < 32). Valid + // addresses in local memory will never be larger than + // 2**255, so they can be safely re-interpreted as signed. + // Note: the first check is always true, + // this could have been a do-while loop. + // solhint-disable-next-line no-empty-blocks + for { + + } slt(dest, dEnd) { + + } { + mstore(dEnd, mload(sEnd)) + sEnd := sub(sEnd, 32) + dEnd := sub(dEnd, 32) + } + + // Write the first 32 bytes + mstore(dest, first) + } + } + } + } + + /// @dev Returns a slices from a byte array. + /// @param b The byte array to take a slice from. + /// @param from The starting index for the slice (inclusive). + /// @param to The final index for the slice (exclusive). + /// @return result The slice containing bytes at indices [from, to) + function slice(bytes memory b, uint256 from, uint256 to) internal pure returns (bytes memory result) { + require(from <= to, "FROM_LESS_THAN_TO_REQUIRED"); + require(to <= b.length, "TO_LESS_THAN_LENGTH_REQUIRED"); + + // Create a new bytes structure and copy contents + result = new bytes(to - from); + memCopy(result.contentAddress(), b.contentAddress() + from, result.length); + return result; + } + + /// @dev Returns a slice from a byte array without preserving the input. + /// @param b The byte array to take a slice from. Will be destroyed in the process. + /// @param from The starting index for the slice (inclusive). + /// @param to The final index for the slice (exclusive). + /// @return result The slice containing bytes at indices [from, to) + /// @dev When `from == 0`, the original array will match the slice. In other cases its state will be corrupted. + function sliceDestructive(bytes memory b, uint256 from, uint256 to) internal pure returns (bytes memory result) { + require(from <= to, "FROM_LESS_THAN_TO_REQUIRED"); + require(to <= b.length, "TO_LESS_THAN_LENGTH_REQUIRED"); + + // Create a new bytes structure around [from, to) in-place. + assembly { + result := add(b, from) + mstore(result, sub(to, from)) + } + return result; + } + + /// @dev Pops the last byte off of a byte array by modifying its length. + /// @param b Byte array that will be modified. + /// @return result The byte that was popped off. + function popLastByte(bytes memory b) internal pure returns (bytes1 result) { + require(b.length > 0, "GREATER_THAN_ZERO_LENGTH_REQUIRED"); + + // Store last byte. + result = b[b.length - 1]; + + assembly { + // Decrement length of byte array. + let newLen := sub(mload(b), 1) + mstore(b, newLen) + } + return result; + } + + /// @dev Pops the last 20 bytes off of a byte array by modifying its length. + /// @param b Byte array that will be modified. + /// @return result The 20 byte address that was popped off. + function popLast20Bytes(bytes memory b) internal pure returns (address result) { + require(b.length >= 20, "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"); + + // Store last 20 bytes. + result = readAddress(b, b.length - 20); + + assembly { + // Subtract 20 from byte array length. + let newLen := sub(mload(b), 20) + mstore(b, newLen) + } + return result; + } + + /// @dev Tests equality of two byte arrays. + /// @param lhs First byte array to compare. + /// @param rhs Second byte array to compare. + /// @return equal True if arrays are the same. False otherwise. + function equals(bytes memory lhs, bytes memory rhs) internal pure returns (bool equal) { + // Keccak gas cost is 30 + numWords * 6. This is a cheap way to compare. + // We early exit on unequal lengths, but keccak would also correctly + // handle this. + return lhs.length == rhs.length && keccak256(lhs) == keccak256(rhs); + } + + /// @dev Reads an address from a position in a byte array. + /// @param b Byte array containing an address. + /// @param index Index in byte array of address. + /// @return result address from byte array. + function readAddress(bytes memory b, uint256 index) internal pure returns (address result) { + require( + b.length >= index + 20, // 20 is length of address + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" + ); + + // Add offset to index: + // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index) + // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index) + index += 20; + + // Read address from array memory + assembly { + // 1. Add index to address of bytes array + // 2. Load 32-byte word from memory + // 3. Apply 20-byte mask to obtain address + result := and(mload(add(b, index)), 0xffffffffffffffffffffffffffffffffffffffff) + } + return result; + } + + /// @dev Writes an address into a specific position in a byte array. + /// @param b Byte array to insert address into. + /// @param index Index in byte array of address. + /// @param input Address to put into byte array. + function writeAddress(bytes memory b, uint256 index, address input) internal pure { + require( + b.length >= index + 20, // 20 is length of address + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" + ); + + // Add offset to index: + // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index) + // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index) + index += 20; + + // Store address into array memory + assembly { + // The address occupies 20 bytes and mstore stores 32 bytes. + // First fetch the 32-byte word where we'll be storing the address, then + // apply a mask so we have only the bytes in the word that the address will not occupy. + // Then combine these bytes with the address and store the 32 bytes back to memory with mstore. + + // 1. Add index to address of bytes array + // 2. Load 32-byte word from memory + // 3. Apply 12-byte mask to obtain extra bytes occupying word of memory where we'll store the address + let neighbors := and( + mload(add(b, index)), + 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 + ) + + // Make sure input address is clean. + // (Solidity does not guarantee this) + input := and(input, 0xffffffffffffffffffffffffffffffffffffffff) + + // Store the neighbors and address into memory + mstore(add(b, index), xor(input, neighbors)) + } + } + + /// @dev Reads a bytes32 value from a position in a byte array. + /// @param b Byte array containing a bytes32 value. + /// @param index Index in byte array of bytes32 value. + /// @return result bytes32 value from byte array. + function readBytes32(bytes memory b, uint256 index) internal pure returns (bytes32 result) { + require(b.length >= index + 32, "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"); + + // Arrays are prefixed by a 256 bit length parameter + index += 32; + + // Read the bytes32 from array memory + assembly { + result := mload(add(b, index)) + } + return result; + } + + /// @dev Writes a bytes32 into a specific position in a byte array. + /// @param b Byte array to insert into. + /// @param index Index in byte array of . + /// @param input bytes32 to put into byte array. + function writeBytes32(bytes memory b, uint256 index, bytes32 input) internal pure { + require(b.length >= index + 32, "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"); + + // Arrays are prefixed by a 256 bit length parameter + index += 32; + + // Read the bytes32 from array memory + assembly { + mstore(add(b, index), input) + } + } + + /// @dev Reads a uint256 value from a position in a byte array. + /// @param b Byte array containing a uint256 value. + /// @param index Index in byte array of uint256 value. + /// @return result uint256 value from byte array. + function readUint256(bytes memory b, uint256 index) internal pure returns (uint256 result) { + result = uint256(readBytes32(b, index)); + return result; + } + + /// @dev Writes a uint256 into a specific position in a byte array. + /// @param b Byte array to insert into. + /// @param index Index in byte array of . + /// @param input uint256 to put into byte array. + function writeUint256(bytes memory b, uint256 index, uint256 input) internal pure { + writeBytes32(b, index, bytes32(input)); + } + + /// @dev Reads an unpadded bytes4 value from a position in a byte array. + /// @param b Byte array containing a bytes4 value. + /// @param index Index in byte array of bytes4 value. + /// @return result bytes4 value from byte array. + function readBytes4(bytes memory b, uint256 index) internal pure returns (bytes4 result) { + require(b.length >= index + 4, "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED"); + + // Arrays are prefixed by a 32 byte length field + index += 32; + + // Read the bytes4 from array memory + assembly { + result := mload(add(b, index)) + // Solidity does not require us to clean the trailing bytes. + // We do it anyway + result := and(result, 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + } + return result; + } + + /// @dev Reads an unpadded bytes8 value from a position in a byte array. + /// @param b Byte array containing a bytes8 value. + /// @param index Index in byte array of bytes4 value. + /// @return result bytes8 value from byte array. + function readBytes8(bytes memory b, uint256 index) internal pure returns (bytes8 result) { + require(b.length >= index + 8, "GREATER_OR_EQUAL_TO_8_LENGTH_REQUIRED"); + + // Arrays are prefixed by a 32 byte length field + index += 32; + + // Read the bytes8 from array memory + assembly { + result := mload(add(b, index)) + // Solidity does not require us to clean the trailing bytes. + // We do it anyway + result := and(result, 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000) + } + return result; + } + + /// @dev Reads an unpadded bytes2 value from a position in a byte array. + /// @param b Byte array containing a bytes2 value. + /// @param index Index in byte array of bytes2 value. + /// @return result bytes2 value from byte array. + function readBytes2(bytes memory b, uint256 index) internal pure returns (bytes2 result) { + require(b.length >= index + 2, "GREATER_OR_EQUAL_TO_2_LENGTH_REQUIRED"); + + // Arrays are prefixed by a 32 byte length field + index += 32; + + // Read the bytes2 from array memory + assembly { + result := mload(add(b, index)) + // Solidity does not require us to clean the trailing bytes. + // We do it anyway + result := and(result, 0xFFFF000000000000000000000000000000000000000000000000000000000000) + } + return result; + } + + /// @dev Reads nested bytes from a specific position. + /// @dev NOTE: the returned value overlaps with the input value. + /// Both should be treated as immutable. + /// @param b Byte array containing nested bytes. + /// @param index Index of nested bytes. + /// @return result Nested bytes. + function readBytesWithLength(bytes memory b, uint256 index) internal pure returns (bytes memory result) { + // Read length of nested bytes + uint256 nestedBytesLength = readUint256(b, index); + index += 32; + + // Assert length of is valid, given + // length of nested bytes + require(b.length >= index + nestedBytesLength, "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"); + + // Return a pointer to the byte array as it exists inside `b` + assembly { + result := add(b, index) + } + return result; + } + + /// @dev Inserts bytes at a specific position in a byte array. + /// @param b Byte array to insert into. + /// @param index Index in byte array of . + /// @param input bytes to insert. + function writeBytesWithLength(bytes memory b, uint256 index, bytes memory input) internal pure { + // Assert length of is valid, given + // length of input + require( + b.length >= index + 32 + input.length, // 32 bytes to store length + "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED" + ); + + // Copy into + memCopy( + b.contentAddress() + index, + input.rawAddress(), // includes length of + input.length + 32 // +32 bytes to store length + ); + } + + /// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length. + /// @param dest Byte array that will be overwritten with source bytes. + /// @param source Byte array to copy onto dest bytes. + function deepCopyBytes(bytes memory dest, bytes memory source) internal pure { + uint256 sourceLen = source.length; + // Dest length must be >= source length, or some bytes would not be copied. + require(dest.length >= sourceLen, "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED"); + memCopy(dest.contentAddress(), source.contentAddress(), sourceLen); + } +} \ No newline at end of file diff --git a/lib/sha512/Sha2Ext.sol b/lib/sha512/Sha2Ext.sol new file mode 100644 index 0000000..56851ba --- /dev/null +++ b/lib/sha512/Sha2Ext.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2023 Paul Razvan Berg + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* +Added SHA-512/256 variant +Copyright (c) 2025 Kadena LLC +*/ + +pragma solidity ^0.8.30; +import { LibBytes } from "./LibBytes.sol"; + +library Sha2Ext { + function sha2(bytes memory message, uint64[8] memory h) internal pure { + uint64[80] memory k = [ + 0x428a2f98d728ae22, + 0x7137449123ef65cd, + 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, + 0x59f111f1b605d019, + 0x923f82a4af194f9b, + 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, + 0x12835b0145706fbe, + 0x243185be4ee4b28c, + 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, + 0x80deb1fe3b1696b1, + 0x9bdc06a725c71235, + 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, + 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, + 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, + 0x5cb0a9dcbd41fbd4, + 0x76f988da831153b5, + 0x983e5152ee66dfab, + 0xa831c66d2db43210, + 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, + 0xd5a79147930aa725, + 0x06ca6351e003826f, + 0x142929670a0e6e70, + 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, + 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, + 0x650a73548baf63de, + 0x766a0abb3c77b2a8, + 0x81c2c92e47edaee6, + 0x92722c851482353b, + 0xa2bfe8a14cf10364, + 0xa81a664bbc423001, + 0xc24b8b70d0f89791, + 0xc76c51a30654be30, + 0xd192e819d6ef5218, + 0xd69906245565a910, + 0xf40e35855771202a, + 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, + 0x1e376c085141ab53, + 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, + 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, + 0x78a5636f43172f60, + 0x84c87814a1f0ab72, + 0x8cc702081a6439ec, + 0x90befffa23631e28, + 0xa4506cebde82bde9, + 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, + 0xca273eceea26619c, + 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, + 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, + 0x1b710b35131c471b, + 0x28db77f523047d84, + 0x32caab7b40c72493, + 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, + 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, + 0x6c44198c4a475817 + ]; + + bytes memory padding = padMessage(message); + require(padding.length % 128 == 0, "PADDING_ERROR"); + uint64[80] memory w; + uint64[8] memory temp; + uint64[16] memory blocks; + uint256 messageLength = (message.length / 128) * 128; + unchecked { + for (uint256 i = 0; i < (messageLength + padding.length); i += 128) { + if (i < messageLength) { + getBlock(message, blocks, i); + } else { + getBlock(padding, blocks, i - messageLength); + } + for (uint256 j = 0; j < 16; ++j) { + w[j] = blocks[j]; + } + for (uint256 j = 16; j < 80; ++j) { + w[j] = gamma1(w[j - 2]) + w[j - 7] + gamma0(w[j - 15]) + w[j - 16]; + } + for (uint256 j = 0; j < 8; ++j) { + temp[j] = h[j]; + } + for (uint256 j = 0; j < 80; ++j) { + uint64 t1 = temp[7] + sigma1(temp[4]) + ch(temp[4], temp[5], temp[6]) + k[j] + w[j]; + uint64 t2 = sigma0(temp[0]) + maj(temp[0], temp[1], temp[2]); + temp[7] = temp[6]; + temp[6] = temp[5]; + temp[5] = temp[4]; + temp[4] = temp[3] + t1; + temp[3] = temp[2]; + temp[2] = temp[1]; + temp[1] = temp[0]; + temp[0] = t1 + t2; + } + for (uint256 j = 0; j < 8; ++j) { + h[j] += temp[j]; + } + } + } + } + + function sha384(bytes memory message) internal pure returns (bytes32, bytes16) { + uint64[8] memory h = [ + 0xcbbb9d5dc1059ed8, + 0x629a292a367cd507, + 0x9159015a3070dd17, + 0x152fecd8f70e5939, + 0x67332667ffc00b31, + 0x8eb44a8768581511, + 0xdb0c2e0d64f98fa7, + 0x47b5481dbefa4fa4 + ]; + sha2(message, h); + return ( + bytes32(abi.encodePacked(bytes8(h[0]), bytes8(h[1]), bytes8(h[2]), bytes8(h[3]))), + bytes16(abi.encodePacked(bytes8(h[4]), bytes8(h[5]))) + ); + } + + function sha512(bytes memory message) internal pure returns (bytes32, bytes32) { + uint64[8] memory h = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179 + ]; + sha2(message, h); + return ( + bytes32(abi.encodePacked(bytes8(h[0]), bytes8(h[1]), bytes8(h[2]), bytes8(h[3]))), + bytes32(abi.encodePacked(bytes8(h[4]), bytes8(h[5]), bytes8(h[6]), bytes8(h[7]))) + ); + } + + function sha512_256(bytes memory message) internal pure returns (bytes32) { + uint64[8] memory h = [ + 0x22312194fc2bf72c, + 0x9f555fa3c84c64c2, + 0x2393b86b6f53b151, + 0x963877195940eabd, + 0x96283ee2a88effe3, + 0xbe5e1e2553863992, + 0x2b0199fc2c85b8aa, + 0x0eb72ddC81c52ca2 + ]; + sha2(message, h); + return ( + bytes32(abi.encodePacked(bytes8(h[0]), bytes8(h[1]), bytes8(h[2]), bytes8(h[3]))) + ); + } + + function padMessage(bytes memory message) internal pure returns (bytes memory) { + uint256 messageLength = message.length; + bytes8 bitLength = bytes8(uint64(messageLength * 8)); + uint256 mdi = messageLength % 128; + uint256 paddingLength; + if (mdi < 112) { + paddingLength = 119 - mdi; + } else { + paddingLength = 247 - mdi; + } + bytes memory padding = new bytes(paddingLength); + bytes memory tail = LibBytes.slice(message, messageLength - mdi, messageLength); + return abi.encodePacked(tail, bytes1(0x80), padding, bitLength); + } + + function getBlock(bytes memory message, uint64[16] memory blocks, uint256 index) internal pure { + for (uint256 i = 0; i < 16; ++i) { + blocks[i] = uint64(LibBytes.readBytes8(message, index + i * 8)); + } + } + + function ch(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) { + return (x & y) ^ (~x & z); + } + + function maj(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) { + return (x & y) ^ (x & z) ^ (y & z); + } + + function sigma0(uint64 x) internal pure returns (uint64) { + return (rotateRight(x, 28) ^ rotateRight(x, 34) ^ rotateRight(x, 39)); + } + + function sigma1(uint64 x) internal pure returns (uint64) { + return (rotateRight(x, 14) ^ rotateRight(x, 18) ^ rotateRight(x, 41)); + } + + function gamma0(uint64 x) internal pure returns (uint64) { + return (rotateRight(x, 1) ^ rotateRight(x, 8) ^ (x >> 7)); + } + + function gamma1(uint64 x) internal pure returns (uint64) { + return (rotateRight(x, 19) ^ rotateRight(x, 61) ^ (x >> 6)); + } + + function rotateRight(uint64 x, uint64 n) internal pure returns (uint64) { + return (x << (64 - n)) | (x >> n); + } +} diff --git a/src/Chainweb.sol b/src/Chainweb.sol index 9927414..ee185ff 100644 --- a/src/Chainweb.sol +++ b/src/Chainweb.sol @@ -5,11 +5,29 @@ import {CommonBase} from "forge-std/Base.sol"; import {console} from "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; import {Script, console} from "forge-std/Script.sol"; +import {Sha512_256Precompile, SHA512_256_ADDRESS} from "./Sha512_256Precompile.sol"; interface IChainwebChainId { function getChainId() external view returns (uint32); } +/** + * @title Chainweb Foundry Support + * @author Kadena LLC engineering team + * @notice This contract can be used to manage Chainweb forks in Foundry. + * + * After creating an instance of this Contract it must be initialized. For that two functions are provided: + * + * - setupChainsForScript() + * - setupChainsForTest() + * + * These functions *MUST NOT* be called in a constructor of a contract that interacts with any of + * the Chainweb chains. For that purpose the `createForksForScript()` and `createForksForTest()` + * functions should be used, which defer initialization until the first time a chain is switched to. + * + * The ChainwebConfigReader contract can be used, which allows to initialize a + * Chainweb from a fork configuration in JSON format. + */ contract Chainweb is CommonBase { uint24 private _numberOfChains; uint256 private _chainIdOffset; @@ -17,6 +35,15 @@ contract Chainweb is CommonBase { uint256[] private _chainForks; string private _hostUrl; + // vm.selectFork must not be called within the constructor of a script. + // Instead we initialize forks the first time switchChain is called for the + // respective chain. + // + // NOTE that this means, that every script or test must call switchChain + // before interacting with the chain. + // + bool[] private _forkIsInitialized; + // @notice Precompile that provides the chainweb-chain-id address public constant CHAIN_ID_PRECOMPILE = address(0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc); @@ -27,35 +54,108 @@ contract Chainweb is CommonBase { _hostUrl = hostUrl; } - function getNodePath() private returns (string memory) { - string[] memory cmds = new string[](5); - cmds[0] = "find"; - cmds[1] = "-L"; - cmds[2] = "."; - cmds[3] = "-name"; - cmds[4] = "node.sh"; - bytes memory output = vm.ffi(cmds); - string memory path = string(output); - return path; + /* ************************************************************************************************************** */ + /* Public Functions */ + + /** + * @notice To be called form setUp() function of a script contract. + * @dev It *MUST NOT* be called from within a constructor of a script that + * utilizes this contract. Instead `createForksForScript()` should be called. + */ + function setupChainsForScript() public { + createForksForScript(); + for (uint256 i = 0; i < _numberOfChains; i++) { + switchChain(i + _chainwebChainIdOffset); + } } - function rpcUrl(string memory chainId, string memory chainwebChainId) private returns (string memory) { - // TODO - support forking from a custom RPC URL if provided - string[] memory cmds = new string[](4); - cmds[0] = getNodePath(); - cmds[1] = "start"; - cmds[2] = chainId; - cmds[3] = chainwebChainId; - bytes memory output = vm.ffi(cmds); - string memory url = string(output); - return url; + /** + * @notice To be called from setUp() function of a test contract. + * @dev It *MUST NOT* be called from within a constructor that utilizes this + * contract. Instead `createForksForTest()` should be called. + */ + function setupChainsForTest() public { + createForksForTest(); + for (uint256 i = 0; i < _numberOfChains; i++) { + switchChain(i + _chainwebChainIdOffset); + } } + /** + * @notice Gets the list of all Chainweb chain IDs. + * @dev It is safe to call this function at any time. + */ + function getChainIds() public view returns (uint256[] memory) { + uint256[] memory chainIds = new uint256[](_numberOfChains); + for (uint24 i = 0; i < _numberOfChains; i++) { + chainIds[i] = i + _chainwebChainIdOffset; + } + return chainIds; + } + + /** + * @notice Switch to a Chainweb chain + * @notice If the chain is not initialized, it will be initialized. + * @dev Under the hood this selects the fork for the respective Chainweb + * chain. This function *MUST NOT* be called from within a constructor of a + * script that utilizes this contract. It is safe to call this functions on + * an uninitialized chain. + */ + function switchChain(uint256 chainId) public { + require(chainId >= _chainwebChainIdOffset, "Invalid chain ID"); + uint24 chainIndex = uint24(chainId - _chainwebChainIdOffset); + require(chainIndex < _numberOfChains, "Invalid chain ID"); + uint256 forkId = _chainForks[chainIndex]; + + // avoid switching if already on the correct fork + try vm.activeFork() returns (uint256 activeForkId) { + if (activeForkId == forkId) { + return; + } + } catch { /* no active fork yet */ } + + vm.selectFork(forkId); + vm.chainId(chainIndex + _chainIdOffset); + + if (!_forkIsInitialized[chainIndex]) { + console.log("Initializing fork for chain:", chainId); + console.log("Deploying SHA512_256 precompile for chain:", chainId); + deploySha512_256Precompile(); + console.log("Deploying ChainWebChainId contract for chain:", chainId); + deployChainWebChainIdContract(chainId); + _forkIsInitialized[chainIndex] = true; + } + + console.log("Switched to chain:", chainId); + } + + /** + * @notice Gets the host URL for the Chainweb node. + * @dev It is safe to call this function at any time. + */ function getHostUrl() public view returns (string memory url) { url = _hostUrl; } - function setupChainsForScript() public { + /** + * @notice Gets the active Chainweb chain ID. + * @dev This calls the ChainWeb precompile to retrieve the active chain ID. + * It is available only after the respective chain got initialized. + */ + function getActiveChainId() public view returns (uint32) { + (bool ok, bytes memory data) = CHAIN_ID_PRECOMPILE.staticcall(""); + require(ok, "getActiveChainId: call failed"); + require(data.length != 0, "getActiveChainId: contract is not deployed"); + require(data.length == 4, "getActiveChainId: invalid response"); + return uint32(bytes4(data)); + } + + /** + * @notice Defines Chainweb chain forks for use in a script contract. + * @dev This only defines the forks, but does not initialize them. It is + * safe to call this from within a constructor. + */ + function createForksForScript() public { if (_chainForks.length > 0) { console.log("Chain forks already set up, skipping setup."); return; @@ -72,32 +172,16 @@ contract Chainweb is CommonBase { console.log("Forking", url); _chainForks.push(vm.createFork(url)); } + _forkIsInitialized.push(false); } } - // set the contract in the same address as devnet - function deployChainWebChainIdContract(uint256 chainId) public { - // get runtime bytecode - switchChain(chainId); - - bytes memory bytecode = hex"5f545f526004601cf3"; - - // place code at the target address - vm.etch(CHAIN_ID_PRECOMPILE, bytecode); - - // set storage slot 0 = desired chainId (e.g., 1337) - vm.store(CHAIN_ID_PRECOMPILE, bytes32(uint256(0)), bytes32(chainId)); - } - - function getActiveChainId() public view returns (uint256) { - (bool ok, bytes memory data) = CHAIN_ID_PRECOMPILE.staticcall(""); - require(ok, "call failed"); - - uint32 chainId = uint32(bytes4(data)); - return chainId; - } - - function setupChainsForTest() public { + /** + * @notice Defines Chainweb chain forks for use in a test contract. + * @dev This only defines the forks, but does not initialize them. It is + * safe to call this from within a constructor. + */ + function createForksForTest() public { if (_chainForks.length > 0) { console.log("Chain forks already set up, skipping setup."); return; @@ -108,29 +192,61 @@ contract Chainweb is CommonBase { for (uint24 i = 0; i < _numberOfChains; i++) { uint256 forkId = vm.createFork(url); _chainForks.push(forkId); - deployChainWebChainIdContract(i + _chainwebChainIdOffset); + _forkIsInitialized.push(false); } } - function switchChain(uint256 chainId) public { - if (getActiveChainId() == chainId) { + + /* ************************************************************************************************************** */ + /* Internal Functions */ + + // set the contract in the same address as devnet + function deployChainWebChainIdContract(uint256 chainId) internal { + + // check whether contract is already deployed + if (CHAIN_ID_PRECOMPILE.code.length > 0) { + console.log("ChainWebChainId contract is already deployed"); return; } - require(chainId >= _chainwebChainIdOffset, "Invalid chain ID"); - uint24 chainIndex = uint24(chainId - _chainwebChainIdOffset); - require(chainIndex < _numberOfChains, "Invalid chain ID"); - uint256 forkId = _chainForks[chainIndex]; - vm.selectFork(forkId); - vm.chainId(chainIndex + _chainIdOffset); // Example offset for chain ID - console.log("Switched to chain:", chainId); + + bytes memory bytecode = hex"5f545f526004601cf3"; + vm.etch(CHAIN_ID_PRECOMPILE, bytecode); + + // set storage slot 0 = desired chainId (e.g., 1337) + vm.store(CHAIN_ID_PRECOMPILE, bytes32(uint256(0)), bytes32(chainId)); } - function getChainIds() public view returns (uint256[] memory) { - uint256[] memory chainIds = new uint256[](_numberOfChains); - for (uint24 i = 0; i < _numberOfChains; i++) { - chainIds[i] = i + _chainwebChainIdOffset; - } - return chainIds; + function deploySha512_256Precompile() internal { + bytes memory bytecode = vm.getDeployedCode("Sha512_256Precompile.sol:Sha512_256Precompile"); + vm.allowCheatcodes(SHA512_256_ADDRESS); + vm.etch(SHA512_256_ADDRESS, bytecode); + } + + /* ************************************************************************************************************** */ + /* Private Functions */ + + function getNodePath() private returns (string memory) { + string[] memory cmds = new string[](5); + cmds[0] = "find"; + cmds[1] = "-L"; + cmds[2] = "."; + cmds[3] = "-name"; + cmds[4] = "node.sh"; + bytes memory output = vm.ffi(cmds); + string memory path = string(output); + return path; + } + + function rpcUrl(string memory chainId, string memory chainwebChainId) private returns (string memory) { + // TODO - support forking from a custom RPC URL if provided + string[] memory cmds = new string[](4); + cmds[0] = getNodePath(); + cmds[1] = "start"; + cmds[2] = chainId; + cmds[3] = chainwebChainId; + bytes memory output = vm.ffi(cmds); + string memory url = string(output); + return url; } } @@ -195,7 +311,8 @@ contract ChainwebTest is Test { constructor(uint24 numberOfChains, uint24 chainwebChainIdOffset) { chainweb = new Chainweb(numberOfChains, block.chainid, chainwebChainIdOffset, ""); - chainweb.setupChainsForTest(); + // vm.makePersistent(address(chainweb)); + chainweb.createForksForTest(); } } @@ -212,6 +329,8 @@ contract ChainwebScript is Script { chainweb = new Chainweb( uint24(config.numberOfChains), block.chainid, uint24(config.chainwebChainIdOffset), config.externalHostUrl ); - chainweb.setupChainsForScript(); + // vm.makePersistent(address(configReader)); + // vm.makePersistent(address(chainweb)); + chainweb.createForksForScript(); } } diff --git a/src/Sha512_256Precompile.sol b/src/Sha512_256Precompile.sol new file mode 100644 index 0000000..ae394bc --- /dev/null +++ b/src/Sha512_256Precompile.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Sha2Ext} from "../lib/sha512/Sha2Ext.sol"; + +address constant SHA512_256_ADDRESS = 0x0000000000000000000000000000000000000420; + + +contract Sha512_256Precompile { + + // Using fallback mocks a precompile that does not use abi encoding. + // Using assembly is an attempt to work around the fact that fallback + // functions are considered state-changing by default. + fallback(bytes calldata) external returns (bytes memory) { + assembly { + let success := 0x00 + let memPtr := 0x20 + let ptr := add(memPtr, 0x60) + let cheatAddr := 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D + + // simulate gas costs + calldatacopy(ptr, 0, calldatasize()) + success := delegatecall(gas(), 0x02, ptr, calldatasize(), memPtr, 0x20) + if iszero(success) { + mstore(0x00, 0x00) + revert(0x00, 0x20) + } + + // pauseGasMetering() + mstore(ptr, 0xd1a5b36facea93d30db646079b53f676e9a13aa4954b79e680b73ef23589aa57) + success := call(gas(), cheatAddr, 0, ptr, 0x04, 0x00, 0x00) + if iszero(success) { + mstore(0x00, 0x01) + revert(0x00, 0x20) + } + + // sha512_256(bytes) + mstore(ptr, 0xf3876c3ca51a830615736ebf3528cd122a2b4de4684e78dc49b723fba8c314f5) + mstore(add(ptr, 0x04), 0x20) // offset + mstore(add(ptr, 0x24), calldatasize()) // length + calldatacopy(add(ptr, 0x44), 0, calldatasize()) // data + let paddedLen := shl(5, shr(5, add(calldatasize(), 31))) + success := delegatecall(gas(), SHA512_256_ADDRESS, ptr, add(0x44, paddedLen), memPtr, 0x60) + if iszero(success) { + mstore(0x00, 0x02) + revert(0x00, 0x20) + } + + // resumeGasMetering() + mstore(ptr, 0x2bcd50e0931d8c67aab7497f38fe957d5ae2899b23707e7fa6f99e62e37e59b3) + success := call(gas(), cheatAddr, 0, ptr, 0x04, 0x00, 0x00) + if iszero(success) { + mstore(0x00, 0x03) + revert(0x00, 0x20) + } + + // implicitely abi decode the return value + return(add(memPtr, 0x40), 0x20) + } + } + + function sha512_256(bytes memory input) public pure returns (bytes memory result) { + result = bytes.concat(Sha2Ext.sha512_256(input)); + } +} +