Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions python_ref/gen_ref_data_hmac_sha512.py
Original file line number Diff line number Diff line change
@@ -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()

27 changes: 27 additions & 0 deletions python_ref/gen_ref_data_sha512.py
Original file line number Diff line number Diff line change
@@ -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()

72 changes: 72 additions & 0 deletions src/Deriver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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";
Expand Down Expand Up @@ -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);
}
}
62 changes: 62 additions & 0 deletions src/Hmac.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading