diff --git a/python_ref/gen_ref_data_hmac_sha512.py b/python_ref/gen_ref_data_hmac_sha512.py new file mode 100644 index 0000000..fc0b483 --- /dev/null +++ b/python_ref/gen_ref_data_hmac_sha512.py @@ -0,0 +1,30 @@ +import hashlib +import hmac + +tests = [ + [bytes.fromhex("010203"), bytes.fromhex("040506")], + [bytes.fromhex("01"*128), bytes.fromhex("04")], + [bytes.fromhex("01"*150), bytes.fromhex("04")], +] + + +if __name__ == "__main__": + for itest, test in enumerate(tests): + key, message = test + + key_hex = key.hex() + message_hex = message.hex() + + hmac_result = hmac.new(key, message, hashlib.sha512).digest() + hmac1_hex = hmac_result[:32].hex() + hmac2_hex = hmac_result[32:].hex() + + print(f"bytes memory key{itest} = hex\"{key_hex}\";") + print(f"bytes memory message{itest} = hex\"{message_hex}\";") + print(f"bytes32 hmac{itest}_1_expected = hex\"{hmac1_hex}\";") + print(f"bytes32 hmac{itest}_2_expected = hex\"{hmac2_hex}\";") + print(f"(bytes32 hmac{itest}_1, bytes32 hmac{itest}_2) = Hmac.hmacSha512(key{itest}, message{itest});") + print(f"assertEq(hmac{itest}_1, hmac{itest}_1_expected);") + print(f"assertEq(hmac{itest}_2, hmac{itest}_2_expected);") + print() + diff --git a/python_ref/gen_ref_data_sha512.py b/python_ref/gen_ref_data_sha512.py new file mode 100644 index 0000000..259da0d --- /dev/null +++ b/python_ref/gen_ref_data_sha512.py @@ -0,0 +1,27 @@ +import hashlib + +tests = [ + "", + "010203", + "ab"*1000, +] + + +if __name__ == "__main__": + for itest, data_hex in enumerate(tests): + data = bytes.fromhex(data_hex) + + # Calculate SHA-512 + sha512_hash = hashlib.sha512(data) + hash1_hex = sha512_hash.digest()[:32].hex() + hash2_hex = sha512_hash.digest()[32:].hex() + + print(f"bytes memory data{itest} = hex\"{data_hex}\";") + print(f"bytes32 hash{itest}_1_expected = hex\"{hash1_hex}\";") + print(f"bytes32 hash{itest}_2_expected = hex\"{hash2_hex}\";") + + print(f"(bytes32 hash{itest}_1, bytes32 hash{itest}_2) = Sha2Ext.sha512(data{itest});") + print(f"assertEq(hash{itest}_1, hash{itest}_1_expected);") + print(f"assertEq(hash{itest}_2, hash{itest}_2_expected);") + print() + diff --git a/src/Deriver.sol b/src/Deriver.sol index a4c3658..535fc91 100644 --- a/src/Deriver.sol +++ b/src/Deriver.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.27; import {EllipticCurve} from "../lib/elliptic-curve-solidity/contracts/EllipticCurve.sol"; +import {Hmac} from "./Hmac.sol"; import {Bech32m} from "./Bech32m.sol"; library Deriver { @@ -16,8 +17,16 @@ library Deriver { uint256 public constant BB = 7; uint256 public constant PP = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; + uint256 public constant NN = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; // END SECP256k1 CONSTANTS + // HardenedKeyStart is the index at which a hardened key starts. Each + // extended key has 2^31 normal child keys and 2^31 hardened child keys. + // Thus the range for normal child keys is [0, 2^31 - 1] and the range + // for hardened child keys is [2^31, 2^32 - 1]. + // Pubkey derivation is only supported for normal(not hardened) child keys. + uint256 public constant HARDENED_KEY_START = 0x80000000; // 2^31 + // sha256("TapTweak") bytes32 public constant SHA256_TAP_TWEAK = hex"e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9"; @@ -152,4 +161,67 @@ library Deriver { return getBtcTaprootAddrFromPubkey(xTweaked, hrp); } + + // Public key derivation works only for normal(not hardened) child keys. + // index < HARDENED_KEY_START + function deriveChildPubkeyBip32( + uint256 px, + uint256 py, + bytes32 chainCode, + uint256 index + ) internal pure returns (uint256, uint256) { + require(index < HARDENED_KEY_START, "Index must be less than HARDENED_KEY_START"); + + bytes1 prefix = 0x02; + if (py % 2 == 1) { + prefix = 0x03; + } + + bytes memory data = abi.encodePacked( + prefix, + bytes32(px), + uint32(index) + ); + + (bytes32 ilBytes32, bytes32 irBytes32) = Hmac.hmacSha512(abi.encodePacked(chainCode), data); + uint256 il = uint256(ilBytes32); + + require(il < NN, "il must be less than NN"); + + (uint256 ilx, uint256 ily) = mulPubkey(GX, GY, il); + (uint256 x1, uint256 y1) = addPubkeys(px, py, ilx, ily); + + require(x1 != 0 || y1 != 0, "child pubkey is point at infinity"); + + return (x1, y1); + } + + function deriveReceivingAddressFromIndex( + uint256 parentX, + uint256 parentY, + uint256 index, + bytes memory hrp + ) internal pure returns (string memory){ + bytes1 prefix = 0x02; + if (parentY % 2 == 1) { + prefix = 0x03; + } + + bytes memory parentSerialized = abi.encodePacked( + prefix, + bytes32(parentX) + ); + + bytes32 chainCode = sha256(parentSerialized); + + (uint256 childX, uint256 childY) = deriveChildPubkeyBip32(parentX, parentY, chainCode, index); + + if (childY % 2 == 1) { + childY = PP - childY; + } + + (uint256 childXTweaked,) = computeTaprootKeyNoScript(childX, childY); + + return getBtcTaprootAddrFromPubkey(childXTweaked, hrp); + } } diff --git a/src/Hmac.sol b/src/Hmac.sol new file mode 100644 index 0000000..72746b6 --- /dev/null +++ b/src/Hmac.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Sha2Ext} from "./sha2/Sha2Ext.sol"; + +library Hmac { + error KeyCannotBeEmpty(); + error MessageCannotBeEmpty(); + + // SHA-512 block size in bytes + uint256 constant BLOCK_SIZE = 128; + + function sha512AsBytes( + bytes memory message + ) internal pure returns (bytes memory) { + (bytes32 hash1, bytes32 hash2) = Sha2Ext.sha512(message); + return abi.encodePacked(hash1, hash2); + } + + function hmacSha512( + bytes memory key, + bytes memory message + ) internal pure returns (bytes32, bytes32) { + if(key.length == 0) { + revert KeyCannotBeEmpty(); + } + if(message.length == 0) { + revert MessageCannotBeEmpty(); + } + + bytes memory paddedKey = key; + + // If key is longer than block size, hash it + if (key.length > BLOCK_SIZE) { + paddedKey = sha512AsBytes(key); + } + + // If key is shorter than block size, pad with zeros + if (paddedKey.length < BLOCK_SIZE) { + bytes memory temp = new bytes(BLOCK_SIZE); + for (uint i = 0; i < paddedKey.length; i++) { + temp[i] = paddedKey[i]; + } + paddedKey = temp; + } + + // Create inner and outer padded keys + bytes memory innerKey = new bytes(BLOCK_SIZE); + bytes memory outerKey = new bytes(BLOCK_SIZE); + + for (uint i = 0; i < BLOCK_SIZE; i++) { + innerKey[i] = bytes1(uint8(paddedKey[i]) ^ 0x36); + outerKey[i] = bytes1(uint8(paddedKey[i]) ^ 0x5c); + } + + // HMAC = H(outerKey || H(innerKey || message)) + bytes memory innerHash = sha512AsBytes(abi.encodePacked(innerKey, message)); + (bytes32 rez1, bytes32 rez2) = Sha2Ext.sha512(abi.encodePacked(outerKey, innerHash)); + + return (rez1, rez2); + } +} diff --git a/src/sha2/LibBytes.sol b/src/sha2/LibBytes.sol new file mode 100644 index 0000000..7c8212b --- /dev/null +++ b/src/sha2/LibBytes.sol @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: MIT + +// Copied from https://github.com/yangfh2004/SolSha2Ext/blob/main/contracts/lib/LibBytes.sol +// Direct import is not possible because it has hardcoded solidity version there + +/* + + 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.27; + +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/src/sha2/Sha2Ext.sol b/src/sha2/Sha2Ext.sol new file mode 100644 index 0000000..9ff535b --- /dev/null +++ b/src/sha2/Sha2Ext.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT + +// Copied from https://github.com/yangfh2004/SolSha2Ext/blob/main/contracts/lib/Sha2Ext.sol +// Direct import is not possible because it has hardcoded solidity version there + +// MIT License + +// 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. + +pragma solidity 0.8.27; + +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 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); + } +} \ No newline at end of file diff --git a/test/Deriver.t.sol b/test/Deriver.t.sol index 70388c5..de7013d 100644 --- a/test/Deriver.t.sol +++ b/test/Deriver.t.sol @@ -310,4 +310,173 @@ contract DeriverTest is Test { ); assertEq(expectedBtcAddr_3, btcAddr_3); } + + function testDeriveChildPubkeyBip32() public pure { + uint256 px0 = 57074945586406715334625111669072956770253198967468104181021882430082100612963; + uint256 py0 = 23142750304142828437953232435448444948300648252183245674679985556457305450420; + bytes32 chainCode0 = hex"93071d00a68b251e2556974c5a9cba5fd3ceebfdf3d6b083978cb3f3072bdc6b"; + uint256 index0 = 2; + (uint256 cx0, uint256 cy0) = Deriver.deriveChildPubkeyBip32( + px0, + py0, + chainCode0, + index0 + ); + assertEq( + cx0, + 74070375407870803772383716147851670902687590661570579425410879994591649090379 + ); + assertEq( + cy0, + 22360565704915606957816728902241275983499976804656018213898267138481100992100 + ); + + uint256 px1 = 30742219803864166600272570996883887958649406957278654474409962972519929889405; + uint256 py1 = 70513211465368901219354942416364416752777296449079386521912399107647951595716; + bytes32 chainCode1 = hex"190e31c385f819a048cabffdb0b3d0dbd3789d788a49cffc68261ef71b8263c8"; + uint256 index1 = 17; + (uint256 cx1, uint256 cy1) = Deriver.deriveChildPubkeyBip32( + px1, + py1, + chainCode1, + index1 + ); + assertEq( + cx1, + 5087411184501414391835649840157068842008489572913385638823261961588855293850 + ); + assertEq( + cy1, + 110550600879399219428880348010790347930829296535905640329050505014535179606341 + ); + + uint256 px2 = 107140830029448792289342525244896270065376582144659706402692854250688314563220; + uint256 py2 = 79935496324080726152605555942072989487007435347931064453233281410555623580053; + bytes32 chainCode2 = hex"bc9a6e462c0f8143a5f66a31cd445d849a7773a54ad7b04fef65497f7961c01a"; + uint256 index2 = 130; + (uint256 cx2, uint256 cy2) = Deriver.deriveChildPubkeyBip32( + px2, + py2, + chainCode2, + index2 + ); + assertEq( + cx2, + 29618804800753246453676863992713689288577981054331407572395640099596072070334 + ); + assertEq( + cy2, + 19012186344758431981077100555529013400608836327149189423685420650092980251608 + ); + + uint256 px3 = 50447842649702692613222804199309429964729864494053468570763353341059104687484; + uint256 py3 = 88549452749358207275533961140370602316997088319769331567603926522170617984074; + bytes32 chainCode3 = hex"502cc99c8c3090ebb776054f2016b18c04fb5ee9a281f7d0a4ed14761d9c0894"; + uint256 index3 = 1027; + (uint256 cx3, uint256 cy3) = Deriver.deriveChildPubkeyBip32( + px3, + py3, + chainCode3, + index3 + ); + assertEq( + cx3, + 39410688582448126829209298776445037307408821414810593880153166428268607576812 + ); + assertEq( + cy3, + 27955881002545452218257578656005771940987221892190183476469781542040442979146 + ); + + uint256 px4 = 83492360506258972363229007168259843798874789271615741208051464981258120268822; + uint256 py4 = 76191252864540787586774708913716597080653872773324753541711814103970596290506; + bytes32 chainCode4 = hex"dd5c8089a398c500c1a23245aba1179ac6d798facf695d7a600301891c8e8760"; + uint256 index4 = 8196; + (uint256 cx4, uint256 cy4) = Deriver.deriveChildPubkeyBip32( + px4, + py4, + chainCode4, + index4 + ); + assertEq( + cx4, + 70050336265972352500022718553585380558030667178344386277189596126374957151588 + ); + assertEq( + cy4, + 42291296715940497887745101979021449602442772175864971502864329278021423114042 + ); + } + + function testDeriveReceivingAddressFromIndex() public pure { + uint256 px0 = 92827281731274008954803586629051298518909571261423619289955380343486580347456; + uint256 py0 = 73102114537722655223140831762751126960901222634499698479313524413007101409342; + uint256 index0 = 2; + string memory addr0 = Deriver.deriveReceivingAddressFromIndex( + px0, + py0, + index0, + bytes("bcrt") + ); + assertEq( + addr0, + "bcrt1pnxsxys8vd0zyznm8hrzukyh9dkvhvalxg9fk6mgd4e4awur2v7uqgrq4sc" + ); + + uint256 px1 = 42043242194725732014842968116051319138334003633183842263306037194578700909017; + uint256 py1 = 105821727192811762117189587761380553359906792616517208870399232874956654784394; + uint256 index1 = 33; + string memory addr1 = Deriver.deriveReceivingAddressFromIndex( + px1, + py1, + index1, + bytes("bcrt") + ); + assertEq( + addr1, + "bcrt1pqmenx5decj6z7w0flwdlapuyqran78w0jq44sk7wkf7s37x3pp7s0yv6qp" + ); + + uint256 px2 = 64704383364590153413480446292465727728387599614170842002472827633593419784476; + uint256 py2 = 100573695907478917642263950404364417572064838031945548826282911915821399827971; + uint256 index2 = 514; + string memory addr2 = Deriver.deriveReceivingAddressFromIndex( + px2, + py2, + index2, + bytes("bcrt") + ); + assertEq( + addr2, + "bcrt1pgpqxw0dagcsxlyae2hj34a0538jnmtvarpwtqm5txwnusnmygpqqucvsvt" + ); + + uint256 px3 = 52672097719492178455192603747575185348778895943012542362274508865272077582372; + uint256 py3 = 22715561287017813787869506730496377822788860365548411913263474592148236937818; + uint256 index3 = 8195; + string memory addr3 = Deriver.deriveReceivingAddressFromIndex( + px3, + py3, + index3, + bytes("bcrt") + ); + assertEq( + addr3, + "bcrt1p97yvrl0dc67l3l42q3894k5x8nx7vt0z7jcjfwxzp066vqm5uk5ssszat7" + ); + + uint256 px4 = 7762222604264485911983061564766040050645982976758130076669678366079793347684; + uint256 py4 = 77842275810050337275361784865384886754506198694389874207460975687961431123894; + uint256 index4 = 131076; + string memory addr4 = Deriver.deriveReceivingAddressFromIndex( + px4, + py4, + index4, + bytes("bcrt") + ); + assertEq( + addr4, + "bcrt1ph246t99e89ruellljuqnekwuf2p523lsmugw2kqu4u4uk32dmsasz086s8" + ); + } } diff --git a/test/HmacSha512.t.sol b/test/HmacSha512.t.sol new file mode 100644 index 0000000..a50caa5 --- /dev/null +++ b/test/HmacSha512.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import {Test, console} from "forge-std/Test.sol"; +import {Hmac} from "../src/Hmac.sol"; + +contract HmacSha512Test is Test { + function testHmacSha512() public pure { + // This test was generated automatically by gen_ref_data_hmac_sha512.py + + bytes memory key0 = hex"010203"; + bytes memory message0 = hex"040506"; + bytes32 hmac0_1_expected = hex"d397d5a94c4f9b1ad5bd483f38d382c965c89e8129c2998d7202c530bf492c60"; + bytes32 hmac0_2_expected = hex"a14ee3202d4bbab087dc9af4aacaede0f4b32dfdfc707756a12a7a750b79b5eb"; + (bytes32 hmac0_1, bytes32 hmac0_2) = Hmac.hmacSha512(key0, message0); + assertEq(hmac0_1, hmac0_1_expected); + assertEq(hmac0_2, hmac0_2_expected); + + bytes + memory key1 = hex"0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; + bytes memory message1 = hex"04"; + bytes32 hmac1_1_expected = hex"c68f8756a0235a67f4f3a1c563025bc6c84389ffb80e4fe3a77344b14104c6d9"; + bytes32 hmac1_2_expected = hex"7816a5522ce5c018d8ef7e8133eaa98579690f49d7e38038fbf13ce51898300e"; + (bytes32 hmac1_1, bytes32 hmac1_2) = Hmac.hmacSha512(key1, message1); + assertEq(hmac1_1, hmac1_1_expected); + assertEq(hmac1_2, hmac1_2_expected); + + bytes + memory key2 = hex"010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; + bytes memory message2 = hex"04"; + bytes32 hmac2_1_expected = hex"4440366771063083654ba2bc822768a9e16210b9443910c77a6fb5ef4978c0a9"; + bytes32 hmac2_2_expected = hex"4c4cbf7bf68e3cd18acb16e3c98bdf6be1efdcb2519ee9c4d66eb8f92eac5838"; + (bytes32 hmac2_1, bytes32 hmac2_2) = Hmac.hmacSha512(key2, message2); + assertEq(hmac2_1, hmac2_1_expected); + assertEq(hmac2_2, hmac2_2_expected); + } +} diff --git a/test/Sha512.t.sol b/test/Sha512.t.sol new file mode 100644 index 0000000..59328cb --- /dev/null +++ b/test/Sha512.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import {Test, console} from "forge-std/Test.sol"; +import {Sha2Ext} from "../src/sha2/Sha2Ext.sol"; + +contract Sha512Test is Test { + function testSha512() public pure { + // This test was generated automatically by gen_ref_data_sha512.py + + bytes memory data0 = hex""; + bytes32 hash0_1_expected = hex"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce"; + bytes32 hash0_2_expected = hex"47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"; + (bytes32 hash0_1, bytes32 hash0_2) = Sha2Ext.sha512(data0); + assertEq(hash0_1, hash0_1_expected); + assertEq(hash0_2, hash0_2_expected); + + bytes memory data1 = hex"010203"; + bytes32 hash1_1_expected = hex"27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb"; + bytes32 hash1_2_expected = hex"51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29"; + (bytes32 hash1_1, bytes32 hash1_2) = Sha2Ext.sha512(data1); + assertEq(hash1_1, hash1_1_expected); + assertEq(hash1_2, hash1_2_expected); + + bytes + memory data2 = hex"abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab"; + bytes32 hash2_1_expected = hex"fd805b4f843ccb741c22186ed4f6f0f12dafc2e6a330458fe84f9ecf1ad1a17c"; + bytes32 hash2_2_expected = hex"a24489d6ae63662bc29f507bd00882573473ccc660ff05f818fe6a720632abfc"; + (bytes32 hash2_1, bytes32 hash2_2) = Sha2Ext.sha512(data2); + assertEq(hash2_1, hash2_1_expected); + assertEq(hash2_2, hash2_2_expected); + } +}