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));
+ }
+}
+