diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d136e77548db7a..2779a2ece4b1f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,7 +39,10 @@ add_library(bls-dash ${CMAKE_CURRENT_SOURCE_DIR}/privatekey.cpp) add_library(blstmp ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/privatekey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/bls.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/chaincode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elements.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extendedprivatekey.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extendedpublickey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/legacy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/schemes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/threshold.cpp diff --git a/src/bls.hpp b/src/bls.hpp index d7fc72a4d835f6..89ecf6af6c135f 100644 --- a/src/bls.hpp +++ b/src/bls.hpp @@ -18,7 +18,10 @@ #include "privatekey.hpp" #include "util.hpp" #include "schemes.hpp" +#include "chaincode.hpp" #include "elements.hpp" +#include "extendedprivatekey.hpp" +#include "extendedpublickey.hpp" #include "hkdf.hpp" #include "hdkeys.hpp" #include "threshold.hpp" diff --git a/src/chaincode.cpp b/src/chaincode.cpp new file mode 100644 index 00000000000000..efacdc95ae29d0 --- /dev/null +++ b/src/chaincode.cpp @@ -0,0 +1,60 @@ +// Copyright 2018 Chia Network Inc + +// 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. + +#include "bls.hpp" + +namespace bls { + +ChainCode ChainCode::FromBytes(const Bytes& bytes) { + if (bytes.size() != ChainCode::SIZE) { + throw std::invalid_argument("ChainCode::FromBytes: Invalid size"); + } + ChainCode c = ChainCode(); + bn_new(c.chainCode); + bn_read_bin(c.chainCode, bytes.begin(), ChainCode::SIZE); + return c; +} + +ChainCode::ChainCode(const ChainCode &cc) { + uint8_t bytes[ChainCode::SIZE]; + cc.Serialize(bytes); + bn_new(chainCode); + bn_read_bin(chainCode, bytes, ChainCode::SIZE); +} + +// Comparator implementation. +bool operator==(ChainCode const &a, ChainCode const &b) { + return bn_cmp(a.chainCode, b.chainCode) == RLC_EQ; +} + +bool operator!=(ChainCode const &a, ChainCode const &b) { + return !(a == b); +} + +std::ostream &operator<<(std::ostream &os, ChainCode const &s) { + uint8_t buffer[ChainCode::SIZE]; + s.Serialize(buffer); + return os << Util::HexStr(buffer, ChainCode::SIZE); +} + +void ChainCode::Serialize(uint8_t *buffer) const { + bn_write_bin(buffer, ChainCode::SIZE, chainCode); +} + +std::vector ChainCode::Serialize() const { + std::vector data(SIZE); + Serialize(data.data()); + return data; +} +} // end namespace bls diff --git a/src/chaincode.hpp b/src/chaincode.hpp new file mode 100644 index 00000000000000..bd3e57b4d77992 --- /dev/null +++ b/src/chaincode.hpp @@ -0,0 +1,59 @@ +// Copyright 2018 Chia Network Inc + +// 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. + +#ifndef SRC_CHAINCODE_HPP_ +#define SRC_CHAINCODE_HPP_ + +#include +#include + +#include "relic_conf.h" + +#if defined GMP && ARITH == GMP +#include +#endif + + +#include "relic.h" +#include "relic_test.h" + +#include "util.hpp" + +namespace bls { +class ChainCode { + public: + static const size_t SIZE = 32; + + static ChainCode FromBytes(const Bytes& bytes); + + ChainCode(const ChainCode &cc); + + // Comparator implementation. + friend bool operator==(ChainCode const &a, ChainCode const &b); + friend bool operator!=(ChainCode const &a, ChainCode const &b); + friend std::ostream &operator<<(std::ostream &os, ChainCode const &s); + + void Serialize(uint8_t *buffer) const; + std::vector Serialize() const; + + // Prevent direct construction, use static constructor + ChainCode() {} +private: + + bn_t chainCode; +}; +} // end namespace bls + +#endif // SRC_CHAINCODE_HPP_ + diff --git a/src/extendedprivatekey.cpp b/src/extendedprivatekey.cpp new file mode 100644 index 00000000000000..6c5800b3998d80 --- /dev/null +++ b/src/extendedprivatekey.cpp @@ -0,0 +1,200 @@ +// Copyright 2018 Chia Network Inc + +// 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. + +#include +#include "bls.hpp" + +namespace bls { + +ExtendedPrivateKey ExtendedPrivateKey::FromSeed(const Bytes& bytes) { + // "BLS HD seed" in ascii + const uint8_t prefix[] = {66, 76, 83, 32, 72, 68, 32, 115, 101, 101, 100}; + + uint8_t* hashInput = Util::SecAlloc(bytes.size() + 1); + std::memcpy(hashInput, bytes.begin(), bytes.size()); + + // 32 bytes for secret key, and 32 bytes for chaincode + uint8_t* ILeft = Util::SecAlloc( + PrivateKey::PRIVATE_KEY_SIZE); + uint8_t IRight[ChainCode::SIZE]; + + // Hash the seed into 64 bytes, half will be sk, half will be cc + hashInput[bytes.size()] = 0; + md_hmac(ILeft, hashInput, bytes.size() + 1, prefix, sizeof(prefix)); + + hashInput[bytes.size()] = 1; + md_hmac(IRight, hashInput, bytes.size() + 1, prefix, sizeof(prefix)); + + // Make sure private key is less than the curve order + bn_t* skBn = Util::SecAlloc(1); + bn_t order; + bn_new(order); + g1_get_ord(order); + + bn_new(*skBn); + bn_read_bin(*skBn, ILeft, PrivateKey::PRIVATE_KEY_SIZE); + bn_mod_basic(*skBn, *skBn, order); + bn_write_bin(ILeft, PrivateKey::PRIVATE_KEY_SIZE, *skBn); + + ExtendedPrivateKey esk(ExtendedPublicKey::VERSION, 0, 0, 0, + ChainCode::FromBytes(Bytes(IRight, ChainCode::SIZE)), + PrivateKey::FromBytes(Bytes(ILeft, PrivateKey::PRIVATE_KEY_SIZE))); + + Util::SecFree(skBn); + Util::SecFree(ILeft); + Util::SecFree(hashInput); + return esk; +} + +ExtendedPrivateKey ExtendedPrivateKey::FromBytes(const Bytes& bytes) { + uint32_t version = Util::FourBytesToInt(bytes.begin()); + uint32_t depth = bytes[4]; + uint32_t parentFingerprint = Util::FourBytesToInt(bytes.begin() + 5); + uint32_t childNumber = Util::FourBytesToInt(bytes.begin() + 9); + const uint8_t* ccPointer = bytes.begin() + 13; + const uint8_t* skPointer = ccPointer + ChainCode::SIZE; + + ExtendedPrivateKey esk(version, depth, parentFingerprint, childNumber, + ChainCode::FromBytes(Bytes(ccPointer, ChainCode::SIZE)), + PrivateKey::FromBytes(Bytes(skPointer, PrivateKey::PRIVATE_KEY_SIZE))); + return esk; +} + +ExtendedPrivateKey ExtendedPrivateKey::PrivateChild(uint32_t i, const bool fLegacy) const { + if (depth >= 255) { + throw std::logic_error("Cannot go further than 255 levels"); + } + // Hardened keys have i >= 2^31. Non-hardened have i < 2^31 + uint32_t cmp = (1 << 31); + bool hardened = i >= cmp; + + uint8_t* ILeft = Util::SecAlloc(PrivateKey::PRIVATE_KEY_SIZE); + uint8_t IRight[ChainCode::SIZE]; + + // Chain code is used as hmac key + uint8_t hmacKey[ChainCode::SIZE]; + chainCode.Serialize(hmacKey); + + size_t inputLen = hardened ? PrivateKey::PRIVATE_KEY_SIZE + 4 + 1 + : G1Element::SIZE + 4 + 1; + // Hmac input includes sk or pk, int i, and byte with 0 or 1 + uint8_t* hmacInput = Util::SecAlloc(inputLen); + + // Fill the input with the required data + if (hardened) { + sk.Serialize(hmacInput); + Util::IntToFourBytes(hmacInput + PrivateKey::PRIVATE_KEY_SIZE, i); + } else { + memcpy(hmacInput, sk.GetG1Element().Serialize(fLegacy).data(), G1Element::SIZE); + Util::IntToFourBytes(hmacInput + G1Element::SIZE, i); + } + hmacInput[inputLen - 1] = 0; + + md_hmac(ILeft, hmacInput, inputLen, + hmacKey, ChainCode::SIZE); + + // Change 1 byte to generate a different sequence for chaincode + hmacInput[inputLen - 1] = 1; + + md_hmac(IRight, hmacInput, inputLen, + hmacKey, ChainCode::SIZE); + + PrivateKey newSk = PrivateKey::FromBytes(Bytes(ILeft, PrivateKey::PRIVATE_KEY_SIZE), true); + newSk = PrivateKey::Aggregate({sk, newSk}); + + ExtendedPrivateKey esk(version, depth + 1, + sk.GetG1Element().GetFingerprint(), i, + ChainCode::FromBytes(Bytes(IRight, ChainCode::SIZE)), + newSk); + + Util::SecFree(ILeft); + Util::SecFree(hmacInput); + + return esk; +} + +uint32_t ExtendedPrivateKey::GetVersion() const { + return version; +} + +uint8_t ExtendedPrivateKey::GetDepth() const { + return depth; +} + +uint32_t ExtendedPrivateKey::GetParentFingerprint() const { + return parentFingerprint; +} + +uint32_t ExtendedPrivateKey::GetChildNumber() const { + return childNumber; +} + +ExtendedPublicKey ExtendedPrivateKey::PublicChild(uint32_t i) const { + return PrivateChild(i).GetExtendedPublicKey(); +} + +PrivateKey ExtendedPrivateKey::GetPrivateKey() const { + return sk; +} + +G1Element ExtendedPrivateKey::GetPublicKey() const { + return sk.GetG1Element(); +} + +ChainCode ExtendedPrivateKey::GetChainCode() const { + return chainCode; +} + +ExtendedPublicKey ExtendedPrivateKey::GetExtendedPublicKey(const bool fLegacy) const { + std::vector buffer(ExtendedPublicKey::SIZE); + Util::IntToFourBytes(buffer.data(), version); + buffer[4] = depth; + Util::IntToFourBytes(buffer.data() + 5, parentFingerprint); + Util::IntToFourBytes(buffer.data() + 9, childNumber); + + chainCode.Serialize(buffer.data() + 13); + auto vecG1 = sk.GetG1Element().Serialize(fLegacy); + buffer.insert(buffer.begin() + 13 + ChainCode::SIZE, vecG1.begin(), vecG1.end()); + + return ExtendedPublicKey::FromBytes(Bytes(buffer), fLegacy); +} + +// Comparator implementation. +bool operator==(ExtendedPrivateKey const &a, ExtendedPrivateKey const &b) { + return (a.GetPrivateKey() == b.GetPrivateKey() && + a.GetChainCode() == b.GetChainCode()); +} + +bool operator!=(ExtendedPrivateKey const&a, ExtendedPrivateKey const&b) { + return !(a == b); +} + +void ExtendedPrivateKey::Serialize(uint8_t *buffer) const { + Util::IntToFourBytes(buffer, version); + buffer[4] = depth; + Util::IntToFourBytes(buffer + 5, parentFingerprint); + Util::IntToFourBytes(buffer + 9, childNumber); + chainCode.Serialize(buffer + 13); + sk.Serialize(buffer + 13 + ChainCode::SIZE); +} + +std::vector ExtendedPrivateKey::Serialize() const { + std::vector data(SIZE); + Serialize(data.data()); + return data; +} + +// Destructors in PrivateKey and ChainCode handle cleaning of memory +ExtendedPrivateKey::~ExtendedPrivateKey() {} +} // end namespace bls diff --git a/src/extendedprivatekey.hpp b/src/extendedprivatekey.hpp new file mode 100644 index 00000000000000..aefdbf0572570a --- /dev/null +++ b/src/extendedprivatekey.hpp @@ -0,0 +1,116 @@ +// Copyright 2018 Chia Network Inc + +// 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. + +#ifndef SRC_EXTENDEDPRIVATEKEY_HPP_ +#define SRC_EXTENDEDPRIVATEKEY_HPP_ + +#include "relic_conf.h" + +#include + +#if defined GMP && ARITH == GMP +#include +#endif + +#include "chaincode.hpp" +#include "elements.hpp" +#include "extendedpublickey.hpp" +#include "privatekey.hpp" + +#include "relic.h" +#include "relic_test.h" + +namespace bls { +/* +Defines a BIP-32 style node, which is composed of a private key and a +chain code. This follows the spec from BIP-0032, with a few changes: + * The master secret key is generated mod n from the master seed, + since not all 32 byte sequences are valid BLS private keys + * Instead of SHA512(input), do SHA256(input || 00000000) || + SHA256(input || 00000001) + * Mod n for the output of key derivation. + * ID of a key is SHA256(pk) instead of HASH160(pk) + * Serialization of extended public key is 93 bytes +*/ +class ExtendedPrivateKey { + public: + // version(4) depth(1) parent fingerprint(4) child#(4) cc(32) sk(32) + static const uint32_t SIZE = 77; + + // Generates a master private key and chain code from a seed + static ExtendedPrivateKey FromSeed(const Bytes& bytes); + + // Parse private key and chain code from bytes + static ExtendedPrivateKey FromBytes(const Bytes& bytes); + + // Derive a child extEnded private key, hardened if i >= 2^31 + ExtendedPrivateKey PrivateChild(uint32_t i, bool fLegacy = true) const; + + // Derive a child extended public key, hardened if i >= 2^31 + ExtendedPublicKey PublicChild(uint32_t i) const; + + uint32_t GetVersion() const; + uint8_t GetDepth() const; + uint32_t GetParentFingerprint() const; + uint32_t GetChildNumber() const; + + ChainCode GetChainCode() const; + PrivateKey GetPrivateKey() const; + + G1Element GetPublicKey() const; + ExtendedPublicKey GetExtendedPublicKey(bool fLegacy = true) const; + + // Compare to different private key + friend bool operator==(const ExtendedPrivateKey& a, + const ExtendedPrivateKey& b); + friend bool operator!=(const ExtendedPrivateKey& a, + const ExtendedPrivateKey& b); + + void Serialize(uint8_t* buffer) const; + std::vector Serialize() const; + + ~ExtendedPrivateKey(); + + // Blank public constructor + ExtendedPrivateKey() + : version(0), + depth(0), + parentFingerprint(0), + childNumber(0), + chainCode(ChainCode()), + sk(PrivateKey()) {} + +private: + // Private constructor, force use of static methods + explicit ExtendedPrivateKey(const uint32_t v, const uint8_t d, + const uint32_t pfp, const uint32_t cn, + const ChainCode code, const PrivateKey key) + : version(v), + depth(d), + parentFingerprint(pfp), + childNumber(cn), + chainCode(code), + sk(key) {} + + uint32_t version; + uint8_t depth; + uint32_t parentFingerprint; + uint32_t childNumber; + + ChainCode chainCode; + PrivateKey sk; +}; +} // end namespace bls + +#endif // SRC_EXTENDEDPRIVATEKEY_HPP_ diff --git a/src/extendedpublickey.cpp b/src/extendedpublickey.cpp new file mode 100644 index 00000000000000..a484dea6462153 --- /dev/null +++ b/src/extendedpublickey.cpp @@ -0,0 +1,136 @@ +// Copyright 2018 Chia Network Inc + +// 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. + +#include "bls.hpp" + +namespace bls { + +ExtendedPublicKey ExtendedPublicKey::FromBytes(const Bytes& bytes, const bool fLegacy) { + uint32_t version = Util::FourBytesToInt(bytes.begin()); + uint32_t depth = bytes[4]; + uint32_t parentFingerprint = Util::FourBytesToInt(bytes.begin() + 5); + uint32_t childNumber = Util::FourBytesToInt(bytes.begin() + 9); + const uint8_t* ccPointer = bytes.begin() + 13; + const uint8_t* pkPointer = ccPointer + ChainCode::SIZE; + + ExtendedPublicKey epk(version, depth, parentFingerprint, childNumber, + ChainCode::FromBytes(Bytes(ccPointer, ChainCode::SIZE)), + G1Element::FromBytes(Bytes(pkPointer, G1Element::SIZE), fLegacy)); + return epk; +} + +ExtendedPublicKey ExtendedPublicKey::PublicChild(uint32_t i, const bool fLegacy) const { + // Hardened children have i >= 2^31. Non-hardened have i < 2^31 + uint32_t cmp = (1 << 31); + if (i >= cmp) { + throw std::invalid_argument("Cannot derive hardened children from public key"); + } + if (depth >= 255) { + throw std::logic_error("Cannot go further than 255 levels"); + } + uint8_t ILeft[PrivateKey::PRIVATE_KEY_SIZE]; + uint8_t IRight[ChainCode::SIZE]; + + // Chain code is used as hmac key + uint8_t hmacKey[ChainCode::SIZE]; + chainCode.Serialize(hmacKey); + + // Public key serialization, i serialization, and one 0 or 1 byte + size_t inputLen = G1Element::SIZE + 4 + 1; + + // Hmac input includes sk or pk, int i, and byte with 0 or 1 + uint8_t hmacInput[G1Element::SIZE + 4 + 1]; + + // Fill the input with the required data + auto vecG1 = pk.Serialize(fLegacy); + memcpy(hmacInput, vecG1.data(), vecG1.size()); + + hmacInput[inputLen - 1] = 0; + Util::IntToFourBytes(hmacInput + G1Element::SIZE, i); + + md_hmac(ILeft, hmacInput, inputLen, + hmacKey, ChainCode::SIZE); + + // Change 1 byte to generate a different sequence for chaincode + hmacInput[inputLen - 1] = 1; + + md_hmac(IRight, hmacInput, inputLen, + hmacKey, ChainCode::SIZE); + + PrivateKey leftSk = PrivateKey::FromBytes(Bytes(ILeft, PrivateKey::PRIVATE_KEY_SIZE), true); + G1Element newPk = pk + leftSk.GetG1Element(); + + ExtendedPublicKey epk(version, depth + 1, + GetPublicKey().GetFingerprint(), i, + ChainCode::FromBytes(Bytes(IRight, ChainCode::SIZE)), + newPk); + + return epk; +} + +uint32_t ExtendedPublicKey::GetVersion() const { + return version; +} + +uint8_t ExtendedPublicKey::GetDepth() const { + return depth; +} + +uint32_t ExtendedPublicKey::GetParentFingerprint() const { + return parentFingerprint; +} + +uint32_t ExtendedPublicKey::GetChildNumber() const { + return childNumber; +} + +ChainCode ExtendedPublicKey::GetChainCode() const { + return chainCode; +} + +G1Element ExtendedPublicKey::GetPublicKey() const { + return pk; +} + +// Comparator implementation. +bool operator==(ExtendedPublicKey const &a, ExtendedPublicKey const &b) { + return (a.GetPublicKey() == b.GetPublicKey() && + a.GetChainCode() == b.GetChainCode()); +} + +bool operator!=(ExtendedPublicKey const&a, ExtendedPublicKey const&b) { + return !(a == b); +} + +std::ostream &operator<<(std::ostream &os, ExtendedPublicKey const &a) { + return os << a.GetPublicKey() << a.GetChainCode(); +} + +void ExtendedPublicKey::Serialize(uint8_t *buffer, const bool fLegacy) const { + Util::IntToFourBytes(buffer, version); + buffer[4] = depth; + Util::IntToFourBytes(buffer + 5, parentFingerprint); + Util::IntToFourBytes(buffer + 9, childNumber); + chainCode.Serialize(buffer + 13); + auto vecG1 = pk.Serialize(fLegacy); + memcpy(buffer + 13 + ChainCode::SIZE, vecG1.data(), vecG1.size()); +} + +std::vector ExtendedPublicKey::Serialize(const bool fLegacy) const { + std::vector data(SIZE); + Serialize(data.data(), fLegacy); + return data; +} + +} // end namespace bls diff --git a/src/extendedpublickey.hpp b/src/extendedpublickey.hpp new file mode 100644 index 00000000000000..c327d5761dcde4 --- /dev/null +++ b/src/extendedpublickey.hpp @@ -0,0 +1,109 @@ +// Copyright 2018 Chia Network Inc + +// 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. + +#ifndef SRC_EXTENDEDPUBLICKEY_HPP_ +#define SRC_EXTENDEDPUBLICKEY_HPP_ + +#include "relic_conf.h" + +#include + +#if defined GMP && ARITH == GMP +#include +#endif + +#include "chaincode.hpp" +#include "elements.hpp" + + +#include "relic.h" +#include "relic_test.h" + +namespace bls { + +/* +Defines a BIP-32 style node, which is composed of a private key and a +chain code. This follows the spec from BIP-0032, with a few changes: + * The master secret key is generated mod n from the master seed, + since not all 32 byte sequences are valid BLS private keys + * Instead of SHA512(input), do SHA256(input || 00000000) || + SHA256(input || 00000001) + * Mod n for the output of key derivation. + * ID of a key is SHA256(pk) instead of HASH160(pk) + * Serialization of extended public key is 93 bytes +*/ +class ExtendedPublicKey { + public: + static const uint32_t VERSION = 1; + + // version(4) depth(1) parent fingerprint(4) child#(4) cc(32) pk(48) + static const uint32_t SIZE = 93; + + // Parse public key and chain code from bytes + static ExtendedPublicKey FromBytes(const Bytes& bytes, bool fLegacy = true); + + // Derive a child extended public key, cannot be hardened + ExtendedPublicKey PublicChild(uint32_t i, bool fLegacy = true) const; + + uint32_t GetVersion() const; + uint8_t GetDepth() const; + uint32_t GetParentFingerprint() const; + uint32_t GetChildNumber() const; + + ChainCode GetChainCode() const; + G1Element GetPublicKey() const; + + // Comparator implementation. + friend bool operator==(ExtendedPublicKey const &a, + ExtendedPublicKey const &b); + friend bool operator!=(ExtendedPublicKey const &a, + ExtendedPublicKey const &b); + friend std::ostream &operator<<(std::ostream &os, + ExtendedPublicKey const &s); + + void Serialize(uint8_t *buffer, bool fLegacy = true) const; + std::vector Serialize(bool fLegacy = true) const; + + // Blank public constructor + ExtendedPublicKey() + : version(0), + depth(0), + parentFingerprint(0), + childNumber(0), + chainCode(ChainCode()), + pk(G1Element()) {} + +private: + // private constructor, force use of static methods + explicit ExtendedPublicKey(const uint32_t v, const uint8_t d, + const uint32_t pfp, const uint32_t cn, + const ChainCode code, const G1Element key) + : version(v), + depth(d), + parentFingerprint(pfp), + childNumber(cn), + chainCode(code), + pk(key) {} + + uint32_t version; + uint8_t depth; + uint32_t parentFingerprint; + uint32_t childNumber; + + ChainCode chainCode; + G1Element pk; +}; +} // end namespace bls + +#endif // SRC_EXTENDEDPUBLICKEY_HPP_ diff --git a/src/test.cpp b/src/test.cpp index b31efcc541eaef..989e9d13a6b932 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -1238,6 +1238,120 @@ TEST_CASE("Schemes") { } } +TEST_CASE("Legacy HD keys") { + SECTION("Should create an extended private key from seed") { + std::vector seed{1, 50, 6, 244, 24, 199, 1, 25}; + ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed)); + + ExtendedPrivateKey esk77 = esk.PrivateChild(77 + (1 << 31)); + ExtendedPrivateKey esk77copy = esk.PrivateChild(77 + (1 << 31)); + + REQUIRE(esk77 == esk77copy); + + ExtendedPrivateKey esk77nh = esk.PrivateChild(77); + + auto eskLong = esk.PrivateChild((1 << 31) + 5) + .PrivateChild(0) + .PrivateChild(0) + .PrivateChild((1 << 31) + 56) + .PrivateChild(70) + .PrivateChild(4); + uint8_t chainCode[32]; + eskLong.GetChainCode().Serialize(chainCode); + } + + + SECTION("Should match derivation through private and public keys") { + std::vector seed{1, 50, 6, 244, 24, 199, 1, 25}; + ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed)); + ExtendedPublicKey epk = esk.GetExtendedPublicKey(); + + G1Element pk1 = esk.PrivateChild(238757).GetPublicKey(); + G1Element pk2 = epk.PublicChild(238757).GetPublicKey(); + + REQUIRE(pk1 == pk2); + + PrivateKey sk3 = esk.PrivateChild(0) + .PrivateChild(3) + .PrivateChild(8) + .PrivateChild(1) + .GetPrivateKey(); + + G1Element pk4 = epk.PublicChild(0) + .PublicChild(3) + .PublicChild(8) + .PublicChild(1) + .GetPublicKey(); + REQUIRE(sk3.GetG1Element() == pk4); + + G2Element sig = LegacySchemeMPL().Sign(sk3, Bytes(seed)); + + REQUIRE(LegacySchemeMPL().Verify(sk3.GetG1Element(), Bytes(seed), sig)); + } + + SECTION("Should prevent hardened pk derivation") { + std::vector seed{1, 50, 6, 244, 24, 199, 1, 25}; + ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed)); + ExtendedPublicKey epk = esk.GetExtendedPublicKey(); + + ExtendedPrivateKey sk = esk.PrivateChild((1 << 31) + 3); + REQUIRE_THROWS(epk.PublicChild((1 << 31) + 3)); + } + + SECTION("Should derive public child from parent") { + std::vector seed{1, 50, 6, 244, 24, 199, 1, 0, 0, 0}; + ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed)); + ExtendedPublicKey epk = esk.GetExtendedPublicKey(); + + ExtendedPublicKey pk1 = esk.PublicChild(13); + ExtendedPublicKey pk2 = epk.PublicChild(13); + + REQUIRE(pk1 == pk2); + } + + SECTION("Should cout structures") { + std::vector seed{1, 50, 6, 244, 24, 199, 1, 0, 0, 0}; + ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed)); + ExtendedPublicKey epk = esk.GetExtendedPublicKey(); + + cout << epk << endl; + cout << epk.GetPublicKey() << endl; + cout << epk.GetChainCode() << endl; + + G2Element sig1 = LegacySchemeMPL().Sign(esk.GetPrivateKey(), Bytes(seed)); + cout << sig1 << endl; + } + + SECTION("Should serialize extended keys") { + std::vector seed{1, 50, 6, 244, 25, 199, 1, 25}; + ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed)); + ExtendedPublicKey epk = esk.GetExtendedPublicKey(); + + G1Element pk1 = esk.PrivateChild(238757).GetPublicKey(); + G1Element pk2 = epk.PublicChild(238757).GetPublicKey(); + + REQUIRE(pk1 == pk2); + + ExtendedPrivateKey sk3 = esk.PrivateChild(0) + .PrivateChild(3) + .PrivateChild(8) + .PrivateChild(1); + + ExtendedPublicKey pk4 = epk.PublicChild(0) + .PublicChild(3) + .PublicChild(8) + .PublicChild(1); + uint8_t buffer1[ExtendedPrivateKey::SIZE]; + uint8_t buffer2[ExtendedPublicKey::SIZE]; + uint8_t buffer3[ExtendedPublicKey::SIZE]; + + sk3.Serialize(buffer1); + sk3.GetExtendedPublicKey().Serialize(buffer2); + pk4.Serialize(buffer3); + REQUIRE(std::memcmp(buffer2, buffer3, ExtendedPublicKey::SIZE) == 0); + } +} + TEST_CASE("Threshold Signatures") { SECTION("Secret Key Shares") { size_t m = 3;