|
| 1 | +// SPDX-License-Identifier: UNLICENSED |
| 2 | +pragma solidity ^0.8.8; |
| 3 | + |
| 4 | +import {IHash} from "src/hash/IHash.sol"; |
| 5 | +import {PKE} from "../src/crypto/encryption.sol"; |
| 6 | +import {Utils} from "src/utils/Utils.sol"; |
| 7 | + |
| 8 | +contract BIP32 { |
| 9 | + // Derivation domain separator for BIP39 keys array (Suave seed) |
| 10 | + bytes public constant BIP32_DERIVATION_DOMAIN = hex"416E64726F6D6564612073656564"; |
| 11 | + // Hardened key indexes start from 0x80000000 (2³¹) and above |
| 12 | + uint32 public constant HARDENED_START_INDEX = 0x80000000; |
| 13 | + |
| 14 | + struct ExtendedKeyAttributes { |
| 15 | + // Index of the key in the parent's children |
| 16 | + uint32 childNumber; |
| 17 | + bytes32 chainCode; |
| 18 | + } |
| 19 | + struct ExtendedPrivateKey { |
| 20 | + bytes32 key; |
| 21 | + ExtendedKeyAttributes attributes; |
| 22 | + } |
| 23 | + struct ExtendedPublicKey { |
| 24 | + bytes key; |
| 25 | + ExtendedKeyAttributes attributes; |
| 26 | + } |
| 27 | + |
| 28 | + IHash private hasher; |
| 29 | + |
| 30 | + constructor(IHash _hasher) { |
| 31 | + hasher = _hasher; |
| 32 | + } |
| 33 | + |
| 34 | + function split(bytes memory data) public pure returns(bytes32 key, bytes32 chain_code) { |
| 35 | + require(data.length == 64, "BIP32: data length must be 64 bytes"); |
| 36 | + assembly { |
| 37 | + // this starts with 32 index because the first 32 bytes are the length of the bytes array |
| 38 | + key := mload(add(data, 32)) // Load first 32 bytes |
| 39 | + chain_code := mload(add(data, 64)) // Load second 32 bytes |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + // Based on the context, it generates either the extended private key or extended public key |
| 44 | + function newFromSeed(bytes memory seed) public view returns (ExtendedPrivateKey memory) { |
| 45 | + // if seed length is not 16 32 or 64 bytes, throw |
| 46 | + require(seed.length == 16 || seed.length == 32 || seed.length == 64, "BIP32: seed length must be 16, 32 or 64 bytes"); |
| 47 | + bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(BIP32_DERIVATION_DOMAIN, seed)))); |
| 48 | + (bytes32 secret_key, bytes32 chain_code) = split(output); |
| 49 | + return ExtendedPrivateKey(secret_key, ExtendedKeyAttributes(0, chain_code)); |
| 50 | + } |
| 51 | + |
| 52 | + // derive extended child key pair from a parent seed |
| 53 | + function deriveChildKeyPairFromSeed(bytes memory seed, uint32 index) external view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) { |
| 54 | + ExtendedPrivateKey memory parent = newFromSeed(seed); |
| 55 | + return deriveChildKeyPair(parent, index); |
| 56 | + } |
| 57 | + |
| 58 | + // derive extended child key pair from a parent extended private key |
| 59 | + function deriveChildKeyPair(ExtendedPrivateKey memory parent, uint32 index) public view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) { |
| 60 | + // hardened key derivation if index > 0x80000000 |
| 61 | + if (index >= 0x80000000) { |
| 62 | + bytes memory data = abi.encodePacked(hex"00", parent.key, index); |
| 63 | + bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data)))); |
| 64 | + (bytes32 secret_key, bytes32 chain_code) = split(output); |
| 65 | + ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code); |
| 66 | + xPriv = ExtendedPrivateKey(secret_key, extKeyAttr); |
| 67 | + xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr); |
| 68 | + } else { |
| 69 | + bytes memory data = abi.encodePacked(PKE.derivePubKey(parent.key), index); |
| 70 | + bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data)))); |
| 71 | + (bytes32 secret_key, bytes32 chain_code) = split(output); |
| 72 | + ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code); |
| 73 | + xPriv = ExtendedPrivateKey(secret_key, extKeyAttr); |
| 74 | + xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + // derive child extended key pairs from a given string path |
| 79 | + function deriveChildKeyPairFromPath(bytes memory seed, string memory path) external view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) { |
| 80 | + bytes memory pathBytes = bytes(path); |
| 81 | + // require that the path is not empty and the first character of the path is "m" or "M" |
| 82 | + require(pathBytes.length > 0 || pathBytes[0] == bytes1('m') || pathBytes[0] == bytes1('M'), "BIP32: invalid path"); |
| 83 | + |
| 84 | + ExtendedPrivateKey memory data = newFromSeed(seed); |
| 85 | + // if the path is just "m" or "M", return the extended private key and extended public key |
| 86 | + if(pathBytes.length <= 2) { |
| 87 | + return (data, ExtendedPublicKey(PKE.derivePubKey(data.key), data.attributes)); |
| 88 | + } |
| 89 | + |
| 90 | + uint32 index = 0; |
| 91 | + // iterate through the path and derive the child key pair at each level and check the occurrence of ' to define hardened derivation or not |
| 92 | + for (uint i = 2; i < pathBytes.length; i++) { |
| 93 | + if (pathBytes[i] == bytes1('\'')) { |
| 94 | + //check if index + 2147483648 does not exceed uint32 max value |
| 95 | + require(index <= 2147483647, "BIP32: invalid path"); |
| 96 | + index += 2147483648; |
| 97 | + } else if (pathBytes[i] == bytes1('/')) { |
| 98 | + (xPriv, xPub) = deriveChildKeyPair(data, index); |
| 99 | + data = xPriv; |
| 100 | + index = 0; |
| 101 | + } else { |
| 102 | + // check if the character is not a number and throw instead of converting it to a number |
| 103 | + require(uint8(pathBytes[i]) >= 48 && uint8(pathBytes[i]) <= 57, "BIP32: invalid path"); |
| 104 | + index = index * 10 + uint32(uint8(pathBytes[i]) - 48); |
| 105 | + // TODO further formating checks are probably needed to avoid invalid formats, such as m/000123 or m/' or m/1'' or m// etc... |
| 106 | + } |
| 107 | + } |
| 108 | + (xPriv, xPub) = deriveChildKeyPair(data, index); |
| 109 | + } |
| 110 | + |
| 111 | + // derive child public key from a parent public key |
| 112 | + function derivePubKeyFromParentPubKey(ExtendedPublicKey memory parent, uint32 index) external view returns (ExtendedPublicKey memory xPub) { |
| 113 | + require(index < 0x80000000, "BIP32: can't derive hardened public keys from a parent public key"); |
| 114 | + bytes memory data = abi.encodePacked(parent.key, index); |
| 115 | + bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data)))); |
| 116 | + (bytes32 secret_key, bytes32 chain_code) = split(output); |
| 117 | + ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code); |
| 118 | + xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr); |
| 119 | + } |
| 120 | + |
| 121 | + // derive public key from a given private key |
| 122 | + function derivePubKey(ExtendedPrivateKey memory xPriv) external view returns (ExtendedPublicKey memory xPub) { |
| 123 | + return ExtendedPublicKey(PKE.derivePubKey(xPriv.key), xPriv.attributes); |
| 124 | + } |
| 125 | +} |
0 commit comments