diff --git a/.gitmodules b/.gitmodules index a889d066cf..47a3058580 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,9 @@ [submodule "vendor/spdlog"] path = vendor/spdlog url = https://github.com/gabime/spdlog.git +[submodule "vendor/mcl"] + path = vendor/mcl + url = https://github.com/herumi/mcl.git +[submodule "vendor/bls"] + path = vendor/bls + url = https://github.com/herumi/bls.git diff --git a/README.md b/README.md index a421f26975..7f1364b692 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ features. Fetch.AI will be delivering regular updates. * MacOS Darwin 10.13x and higher (64bit) * Ubuntu 18.04 (x86_64) -(We plan to support all major platforms in the future) +(We plan to support all major platforms in the future). ## Getting Started diff --git a/apps/constellation/CMakeLists.txt b/apps/constellation/CMakeLists.txt index 61d35ad221..2addc87299 100644 --- a/apps/constellation/CMakeLists.txt +++ b/apps/constellation/CMakeLists.txt @@ -14,5 +14,5 @@ add_executable(constellation health_check_http_module.hpp logging_http_module.hpp main.cpp) -target_link_libraries(constellation PRIVATE fetch-ledger fetch-miner) +target_link_libraries(constellation PRIVATE fetch-ledger fetch-miner fetch-dkg) target_include_directories(constellation PRIVATE ${FETCH_ROOT_DIR}/libs/python/include) diff --git a/apps/constellation/constellation.cpp b/apps/constellation/constellation.cpp index 255a527f25..b55fcfffa1 100644 --- a/apps/constellation/constellation.cpp +++ b/apps/constellation/constellation.cpp @@ -26,6 +26,7 @@ #include "ledger/chaincode/contract_http_interface.hpp" #include "ledger/dag/dag_interface.hpp" +#include "dkg/dkg_service.hpp" #include "ledger/consensus/naive_entropy_generator.hpp" #include "ledger/consensus/stake_snapshot.hpp" #include "ledger/execution_manager.hpp" @@ -58,6 +59,7 @@ using fetch::network::Uri; using fetch::network::Peer; using fetch::ledger::Address; using fetch::ledger::GenesisFileCreator; +using fetch::muddle::MuddleEndpoint; using ExecutorPtr = std::shared_ptr; @@ -67,6 +69,7 @@ namespace { using LaneIndex = fetch::ledger::LaneIdentity::lane_type; using StakeManagerPtr = std::shared_ptr; using EntropyPtr = std::unique_ptr; +using DkgServicePtr = std::unique_ptr; using ConstByteArray = byte_array::ConstByteArray; static const std::size_t HTTP_THREADS{4}; @@ -163,6 +166,21 @@ StakeManagerPtr CreateStakeManager(bool enabled, ledger::EntropyGeneratorInterfa return mgr; } +DkgServicePtr CreateDkgService(Constellation::Config const &cfg, ConstByteArray address, + MuddleEndpoint &endpoint) +{ + DkgServicePtr dkg{}; + + if (cfg.proof_of_stake && !cfg.beacon_address.empty()) + { + crypto::bls::Init(); + + dkg = std::make_unique(endpoint, address, cfg.beacon_address); + } + + return dkg; +} + } // namespace /** @@ -199,6 +217,7 @@ Constellation::Constellation(CertificatePtr certificate, Config config) cfg_.log2_num_lanes)) , lane_control_(internal_muddle_.AsEndpoint(), shard_cfgs_, cfg_.log2_num_lanes) , dag_{GenerateDAG(cfg_.features.IsEnabled("synergetic"), "dag_db_", true, certificate)} + , dkg_{CreateDkgService(cfg_, certificate->identity().identifier(), muddle_.AsEndpoint())} , entropy_{CreateEntropy()} , stake_{CreateStakeManager(cfg_.proof_of_stake, *entropy_)} , execution_manager_{std::make_shared( @@ -271,6 +290,12 @@ Constellation::Constellation(CertificatePtr certificate, Config config) { http_.AddModule(*module); } + + // If we are using the DKG service we need to update the default entropy engine for PoS + if (dkg_) + { + stake_->UpdateEntropy(*dkg_); + } } /** @@ -360,7 +385,7 @@ void Constellation::Run(UriList const &initial_peers, core::WeakRunnable bootstr { FETCH_LOG_INFO(LOGGING_NAME, "Loading from genesis save file."); - GenesisFileCreator creator(block_coordinator_, *storage_, stake_.get()); + GenesisFileCreator creator(block_coordinator_, *storage_, stake_.get(), dkg_.get()); creator.LoadFile(SNAPSHOT_FILENAME); FETCH_LOG_INFO(LOGGING_NAME, "Loaded from genesis save file."); @@ -393,10 +418,21 @@ void Constellation::Run(UriList const &initial_peers, core::WeakRunnable bootstr // Step 2. Main monitor loop //--------------------------------------------------------------- bool start_up_in_progress{true}; + bool dkg_attached{false}; // monitor loop while (active_) { + // wait for at least one connected peer + if (!muddle_.AsEndpoint().GetDirectlyConnectedPeers().empty()) + { + if (dkg_ && !dkg_attached) + { + reactor_.Attach(dkg_->GetWeakRunnable()); + dkg_attached = true; + } + } + // determine the status of the main chain server bool const is_in_sync = main_chain_service_->IsSynced() && block_coordinator_.IsSynced(); @@ -440,7 +476,7 @@ void Constellation::Run(UriList const &initial_peers, core::WeakRunnable bootstr { FETCH_LOG_INFO(LOGGING_NAME, "Creating genesis save file."); - GenesisFileCreator creator(block_coordinator_, *storage_, stake_.get()); + GenesisFileCreator creator(block_coordinator_, *storage_, stake_.get(), dkg_.get()); creator.CreateFile(SNAPSHOT_FILENAME); } diff --git a/apps/constellation/constellation.hpp b/apps/constellation/constellation.hpp index eac132d653..e3e77c9b32 100644 --- a/apps/constellation/constellation.hpp +++ b/apps/constellation/constellation.hpp @@ -53,6 +53,10 @@ #include namespace fetch { +namespace dkg { + +class DkgService; +} /** * Top level container for all components that are required to run a ledger instance @@ -66,31 +70,33 @@ class Constellation : public ledger::BlockSinkInterface using Manifest = network::Manifest; using NetworkMode = ledger::MainChainRpcService::Mode; using FeatureFlags = core::FeatureFlags; + using ConstByteArray = byte_array::ConstByteArray; static constexpr uint32_t DEFAULT_BLOCK_DIFFICULTY = 6; struct Config { - Manifest manifest{}; - uint32_t log2_num_lanes{0}; - uint32_t num_slices{0}; - uint32_t num_executors{0}; - std::string interface_address{}; - std::string db_prefix{}; - uint32_t processor_threads{0}; - uint32_t verification_threads{0}; - uint32_t max_peers{0}; - uint32_t transient_peers{0}; - uint32_t block_interval_ms{0}; - uint32_t block_difficulty{DEFAULT_BLOCK_DIFFICULTY}; - uint32_t peers_update_cycle_ms{0}; - bool disable_signing{false}; - bool sign_broadcasts{false}; - bool dump_state_file{false}; - bool load_state_file{false}; - bool proof_of_stake{false}; - NetworkMode network_mode{NetworkMode::PUBLIC_NETWORK}; - FeatureFlags features{}; + Manifest manifest{}; + uint32_t log2_num_lanes{0}; + uint32_t num_slices{0}; + uint32_t num_executors{0}; + std::string interface_address{}; + std::string db_prefix{}; + uint32_t processor_threads{0}; + uint32_t verification_threads{0}; + uint32_t max_peers{0}; + uint32_t transient_peers{0}; + uint32_t block_interval_ms{0}; + uint32_t block_difficulty{DEFAULT_BLOCK_DIFFICULTY}; + uint32_t peers_update_cycle_ms{0}; + bool disable_signing{false}; + bool sign_broadcasts{false}; + bool dump_state_file{false}; + bool load_state_file{false}; + bool proof_of_stake{false}; + NetworkMode network_mode{NetworkMode::PUBLIC_NETWORK}; + ConstByteArray beacon_address{}; + FeatureFlags features{}; uint32_t num_lanes() const { @@ -138,6 +144,7 @@ class Constellation : public ledger::BlockSinkInterface using NaiveSynergeticMiner = ledger::NaiveSynergeticMiner; using StakeManagerPtr = std::shared_ptr; using EntropyPtr = std::unique_ptr; + using DkgServicePtr = std::shared_ptr; using ShardConfigs = ledger::ShardConfigs; using TxStatusCache = ledger::TransactionStatusCache; @@ -178,6 +185,7 @@ class Constellation : public ledger::BlockSinkInterface /// @name Staking /// @{ + DkgServicePtr dkg_; ///< The DKG system EntropyPtr entropy_; ///< The entropy system StakeManagerPtr stake_; ///< The stake system /// @} diff --git a/apps/constellation/main.cpp b/apps/constellation/main.cpp index 985ec3bb36..6260c4a1d3 100644 --- a/apps/constellation/main.cpp +++ b/apps/constellation/main.cpp @@ -234,6 +234,7 @@ struct CommandLineArguments std::string config_path; std::string raw_peers; std::string experimental_features; + std::string beacon_address; ManifestPtr manifest; uint32_t num_lanes{0}; @@ -268,6 +269,7 @@ struct CommandLineArguments p.add(private_flag, "private-network", "Run node as part of a private network (disables bootstrap). Incompatible with -standalone", false); p.add(experimental_features, "experimental", "Enable selected experimental features", std::string{}); p.add(args.cfg.proof_of_stake, "pos", "Enable proof of stake features", false); + p.add(beacon_address, "beacon", "The base64 encoded address of the beacon", std::string{}); // clang-format on // parse the args @@ -303,11 +305,18 @@ struct CommandLineArguments UpdateConfigFromEnvironment(private_flag, "CONSTELLATION_PRIVATE_NETWORK"); UpdateConfigFromEnvironment(experimental_features, "CONSTELLATION_EXPERIMENTAL"); UpdateConfigFromEnvironment(args.cfg.proof_of_stake, "CONSTELLATION_POS"); + UpdateConfigFromEnvironment(beacon_address, "CONSTELLATION_BEACON_ADDRESS"); // clang-format on // parse the feature flags (if they exist) args.cfg.features.Parse(experimental_features); + // beacon address + if (!beacon_address.empty()) + { + args.cfg.beacon_address = fetch::byte_array::FromBase64(beacon_address); + } + // update the peers args.SetPeers(raw_peers); diff --git a/cmake/BuildTargets.cmake b/cmake/BuildTargets.cmake index 941bd173b4..451fc54a22 100644 --- a/cmake/BuildTargets.cmake +++ b/cmake/BuildTargets.cmake @@ -224,6 +224,37 @@ function (configure_vendor_targets) # Google Test add_subdirectory(${FETCH_ROOT_VENDOR_DIR}/googletest) + # MCL + set(USE_GMP OFF CACHE BOOL "use gmp" FORCE) + set(USE_OPENSSL OFF CACHE BOOL "use openssl" FORCE) + # TODO: Work out how to get this to work with the already found version of OpenSSL + + add_subdirectory(${FETCH_ROOT_VENDOR_DIR}/mcl) + target_include_directories(mcl INTERFACE ${FETCH_ROOT_VENDOR_DIR}/mcl/include) + target_compile_definitions(mcl + INTERFACE + -DMCL_USE_VINT + -DMCL_VINT_FIXED_BUFFER) + + add_library(vendor-mcl INTERFACE) + target_link_libraries(vendor-mcl INTERFACE mcl) + target_compile_definitions(vendor-mcl INTERFACE -DMCLBN_FP_UNIT_SIZE=4) + + # BLS + add_library(libbls-internal STATIC ${FETCH_ROOT_VENDOR_DIR}/bls/src/bls_c256.cpp + ${FETCH_ROOT_VENDOR_DIR}/bls/src/bls_c384.cpp + ${FETCH_ROOT_VENDOR_DIR}/bls/src/bls_c384_256.cpp) + target_link_libraries(libbls-internal PUBLIC mcl) + target_include_directories(libbls-internal PUBLIC ${FETCH_ROOT_VENDOR_DIR}/bls/include) + target_compile_definitions(libbls-internal + PUBLIC + -DMCL_USE_VINT + -DMCL_VINT_FIXED_BUFFER) + + add_library(vendor-bls INTERFACE) + target_link_libraries(vendor-bls INTERFACE libbls-internal) + target_compile_definitions(vendor-bls INTERFACE -DMCLBN_FP_UNIT_SIZE=4) + # Google Benchmark Do not build the google benchmark library tests if (FETCH_ENABLE_BENCHMARKS) set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Suppress google benchmark default tests" FORCE) diff --git a/libs/core/include/core/containers/mapping.hpp b/libs/core/include/core/containers/mapping.hpp new file mode 100644 index 0000000000..942ac213b5 --- /dev/null +++ b/libs/core/include/core/containers/mapping.hpp @@ -0,0 +1,111 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 + +namespace fetch { +namespace core { + +template +class Mapping +{ +public: + // Construction / Destruction + Mapping() = default; + Mapping(Mapping const &) = default; + Mapping(Mapping &&) noexcept = default; + ~Mapping() = default; + + bool Lookup(Key const &key, Value &value); + bool Lookup(Value const &value, Key &key); + void Update(Key const &key, Value const &value); + + // Operators + Mapping &operator=(Mapping const &) = default; + Mapping &operator=(Mapping &&) noexcept = default; + +private: + using ForwardIndex = std::unordered_map; + using ReverseIndex = std::unordered_map; + + ForwardIndex forward_index_; + ReverseIndex reverse_index_; + + static_assert(!std::is_same::value, ""); +}; + +template +bool Mapping::Lookup(K const &key, V &value) +{ + bool success{false}; + + auto const it = forward_index_.find(key); + if (it != forward_index_.end()) + { + value = it->second; + success = true; + } + + return success; +} + +template +bool Mapping::Lookup(V const &value, K &key) +{ + bool success{false}; + + auto const it = reverse_index_.find(value); + if (it != reverse_index_.end()) + { + key = it->second; + success = true; + } + + return success; +} + +template +void Mapping::Update(K const &key, V const &value) +{ + // update the forward map + auto const forward_it = forward_index_.find(key); + if (forward_it == forward_index_.end()) + { + forward_index_.emplace(key, value); + } + else + { + forward_it->second = value; + } + + // update the reverse map + auto const reverse_it = reverse_index_.find(value); + if (reverse_it == reverse_index_.end()) + { + reverse_index_.emplace(value, key); + } + else + { + reverse_it->second = key; + } +} + +} // namespace core +} // namespace fetch diff --git a/libs/core/include/core/service_ids.hpp b/libs/core/include/core/service_ids.hpp index c98503c3cc..dc4e32aefb 100644 --- a/libs/core/include/core/service_ids.hpp +++ b/libs/core/include/core/service_ids.hpp @@ -28,6 +28,7 @@ static constexpr uint16_t SERVICE_LANE = 3003; static constexpr uint16_t SERVICE_LANE_CTRL = 3004; static constexpr uint16_t SERVICE_EXECUTOR = 4004; static constexpr uint16_t SERVICE_DAG = 4005; +static constexpr uint16_t SERVICE_DKG = 5001; // Common Service Channels static constexpr uint16_t CHANNEL_RPC = 1; // for convenience we essentially @@ -47,6 +48,10 @@ static constexpr uint16_t CHANNEL_BLOCKS = 2; static constexpr uint16_t CHANNEL_NODES = 300; static constexpr uint64_t CHANNEL_RPC_BROADCAST = 301; +// DKG Service Channels +static constexpr uint16_t CHANNEL_SECRET_KEY = 400; +static constexpr uint16_t CHANNEL_CONTRIBUTIONS = 401; + // RPC Protocol identifiers static constexpr uint64_t RPC_MAIN_CHAIN = 199; @@ -62,4 +67,6 @@ static constexpr uint64_t RPC_EXECUTOR = 208; static constexpr uint64_t RPC_P2P_RESOLVER = 209; static constexpr uint64_t RPC_MISSING_TX_FINDER = 210; static constexpr uint64_t RPC_DAG_STORE_SYNC = 211; +static constexpr uint64_t RPC_DKG_BEACON = 212; + } // namespace fetch diff --git a/libs/crypto/CMakeLists.txt b/libs/crypto/CMakeLists.txt index 8d3078904d..c6bf6c90e0 100644 --- a/libs/crypto/CMakeLists.txt +++ b/libs/crypto/CMakeLists.txt @@ -20,7 +20,8 @@ target_link_libraries(fetch-crypto fetch-meta fetch-math fetch-vectorise - vendor-openssl) + vendor-openssl + vendor-bls) add_test_target() diff --git a/libs/crypto/examples/bls/main.cpp b/libs/crypto/examples/bls/main.cpp new file mode 100644 index 0000000000..d3169fa65c --- /dev/null +++ b/libs/crypto/examples/bls/main.cpp @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "crypto/bls.hpp" +#include +using namespace fetch::crypto; + +int main() +{ + bls::init(); + std::cout << "Running BLS signature" << std::endl; + BLSSigner signer; + signer.GenerateKeys(); + + BLSVerifier verifier{signer.identity()}; + auto sig = signer.Sign("Hello world"); + + if (verifier.Verify("Hello world", sig)) + { + std::cout << "Signature verified." << std::endl; + } + return 0; +} diff --git a/libs/crypto/examples/dkg/main.cpp b/libs/crypto/examples/dkg/main.cpp new file mode 100644 index 0000000000..696d58d29e --- /dev/null +++ b/libs/crypto/examples/dkg/main.cpp @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "crypto/bls_base.hpp" +#include "crypto/bls_dkg.hpp" +#include + +using namespace fetch::crypto; +using namespace fetch::byte_array; + +struct Member +{ + ConstByteArray seed; + bls::Id id; + bls::PrivateKey sk; + bls::PrivateKeyList received_shares; + bls::PrivateKey secret_key_share; +}; + +void Test() +{ + ConstByteArray message = "Hello world"; + auto private_key = bls::HashToPrivateKey("my really long phrase to generate a key"); + auto public_key = bls::PublicKeyFromPrivate(private_key); + + auto signature = bls::Sign(private_key, message); + if (bls::Verify(signature, public_key, message)) + { + std::cout << "'Hello world' was signed." << std::endl; + } +} + +int main() +{ + using VerificationVector = bls::dkg::VerificationVector; + using ParticipantVector = bls::dkg::ParticipantVector; + + bls::Init(); + + // Creating members from predefined seeds + std::vector member_seeds = {"12122", "454323", "547456", "54", + "23423423", "68565", "56465"}; + std::vector members; + uint32_t threshold = 4; + + for (auto &seed : member_seeds) + { + Member member; + member.seed = seed; + member.sk = bls::HashToPrivateKey(seed); + members.push_back(member); + } + + // Creating participant list + ParticipantVector participants; + for (auto &member : members) + { + member.id.v = member.sk.v; + participants.push_back(member.id); + } + + // Generating secret and collecting secrets. + std::vector verification_vectors; + for (uint64_t i = 0; i < members.size(); ++i) + { + auto contrib = bls::dkg::GenerateContribution(participants, threshold); + // Note that the verfication vector can be posted publicly. + verification_vectors.push_back(contrib.verification); + + for (uint64_t i = 0; i < contrib.contributions.size(); ++i) + { + auto spk = contrib.contributions[i]; + auto &member = members[i]; + bool verified = bls::dkg::VerifyContributionShare(member.id, spk, contrib.verification); + + if (!verified) + { + throw std::runtime_error("share could not be verified."); + } + + member.received_shares.push_back(spk); + } + } + + // Generating secret key share + for (auto &member : members) + { + member.secret_key_share = bls::dkg::AccumulateContributionShares(member.received_shares); + } + + // We now use the publicly disclosed verification vectors to generate group vectors. + VerificationVector group_vectors = bls::dkg::AccumulateVerificationVectors(verification_vectors); + + // The groups public key is the first vector. + auto groups_pk = group_vectors[0]; + + // We now sign the message + ConstByteArray message = "Hello world"; + bls::SignatureList signatures; + bls::IdList signer_ids; + + for (uint64_t i = 0; i < threshold; ++i) + { + auto private_key = members[i].secret_key_share; + auto signature = bls::Sign(private_key, message); + auto public_key = bls::PublicKeyFromPrivate(private_key); + + if (!bls::Verify(signature, public_key, message)) + { + throw std::runtime_error("Failed to sign using share"); + } + + signatures.push_back(signature); + signer_ids.push_back(members[i].id); + } + + // And finally we test the signature + auto signature = bls::RecoverSignature(signatures, signer_ids); + if (bls::Verify(signature, groups_pk, message)) + { + std::cout << " -> Hurray, the signature is valid!" << std::endl; + } + else + { + throw std::runtime_error("Signature not working!!"); + } + + return 0; +} diff --git a/libs/crypto/include/crypto/bls.hpp b/libs/crypto/include/crypto/bls.hpp new file mode 100644 index 0000000000..031f7d64d3 --- /dev/null +++ b/libs/crypto/include/crypto/bls.hpp @@ -0,0 +1,166 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "core/byte_array/const_byte_array.hpp" +#include "core/threading/synchronised_state.hpp" +#include "crypto/ecdsa_signature.hpp" +#include "crypto/prover.hpp" +#include "crypto/signature_register.hpp" +#include "crypto/verifier.hpp" + +#include "fetch_bls.hpp" + +#include +#include +#include + +namespace fetch { +namespace crypto { + +namespace details { + +// TODO(issue 1287): This entire file should be updated using the `bls_base.hpp` instead. + +struct BLSInitialiser +{ + BLSInitialiser() + { + bool a{true}; + a = was_initialised.exchange(a); + if (!a) + { + // use BN254 + bls::init(); + } + } + + static std::atomic was_initialised; +}; + +std::atomic BLSInitialiser::was_initialised{false}; +} // namespace details + +class BLSVerifier : public Verifier +{ +public: + explicit BLSVerifier(Identity ident) + : identity_{std::move(ident)} + { + details::BLSInitialiser(); + + std::stringstream buffer(static_cast(identity_.identifier())); + buffer >> public_key_; + } + + bool Verify(ConstByteArray const &data, ConstByteArray const &signature) override + { + if (!identity_) + { + return false; + } + + if (signature.empty()) + { + return false; + } + + bls::Signature sign; + std::stringstream ssig{static_cast(signature)}; + ssig >> sign; + + return sign.verify(public_key_, static_cast(data)); + } + + Identity identity() override + { + return identity_; + } + + operator bool() const + { + return identity_; + } + +private: + Identity identity_; + bls::PublicKey public_key_; +}; + +class BLSSigner : public Prover +{ +public: + BLSSigner() + { + details::BLSInitialiser(); + } + + explicit BLSSigner(ConstByteArray const &private_key) + { + Load(private_key); + } + + void Load(ConstByteArray const &private_key) override + { + std::stringstream buffer(static_cast(private_key)); + buffer >> private_key_; + private_key_.getPublicKey(public_key_); + } + + void GenerateKeys() + { + private_key_.init(); + private_key_.getPublicKey(public_key_); + } + + ConstByteArray Sign(ConstByteArray const &text) const final + { + std::string m = static_cast(text); + bls::Signature s; + private_key_.sign(s, m.c_str()); + std::stringstream signature; + signature << s; + return static_cast(signature.str()); + } + + Identity identity() const final + { + return Identity(BLS_BN256_UNCOMPRESSED, public_key()); + } + + ConstByteArray public_key() const + { + std::stringstream buffer; + buffer << public_key_; + return static_cast(buffer.str()); + } + + ConstByteArray private_key() + { + std::stringstream buffer; + buffer << private_key_; + return static_cast(buffer.str()); + } + +private: + bls::SecretKey private_key_; + bls::PublicKey public_key_; +}; + +} // namespace crypto +} // namespace fetch diff --git a/libs/crypto/include/crypto/bls_base.hpp b/libs/crypto/include/crypto/bls_base.hpp new file mode 100644 index 0000000000..d6be5417d7 --- /dev/null +++ b/libs/crypto/include/crypto/bls_base.hpp @@ -0,0 +1,227 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "core/byte_array/byte_array.hpp" +#include + +namespace fetch { +namespace crypto { + +namespace bls { +using PrivateKey = blsSecretKey; +using PublicKey = blsPublicKey; +using Id = blsId; +using Signature = blsSignature; + +using PublicKeyList = std::vector; +using PrivateKeyList = std::vector; +using IdList = std::vector; +using SignatureList = std::vector; + +enum +{ + E_MCLBN_CURVE_FP254BNB = 0, + E_MCLBN_CURVE_FP382_1 = 1, + E_MCLBN_CURVE_FP382_2 = 2, + E_MCL_BLS12_381 = 5, + E_MCLBN_FP_UNIT_SIZE = 6, + E_FR_SIZE = E_MCLBN_FP_UNIT_SIZE * 8, + E_ID_SIZE = E_FR_SIZE, + E_G1_SIZE = E_FR_SIZE * 3, + E_G2_SIZE = E_FR_SIZE * 3 * 2 +}; + +void Init(); + +inline PrivateKey PrivateKeyByCSPRNG() +{ + PrivateKey ret; + + int32_t error = blsSecretKeySetByCSPRNG(&ret); + + if (error != 0) + { + throw std::runtime_error("Failed at generating BLS secret key."); + } + + return ret; +} + +inline PublicKey PublicKeyFromPrivate(PrivateKey const &priv) +{ + PublicKey ret; + blsGetPublicKey(&ret, &priv); + return ret; +} + +inline Signature Sign(PrivateKey const &priv, byte_array::ConstByteArray const &msg) +{ + Signature ret; + blsSign(&ret, &priv, msg.pointer(), msg.size()); + return ret; +} + +inline bool Verify(Signature const &signature, PublicKey const &pub, + byte_array::ConstByteArray const &msg) +{ + return blsVerify(&signature, &pub, msg.pointer(), msg.size()); +} + +inline PrivateKey HashToPrivateKey(byte_array::ConstByteArray const &seed) +{ + PrivateKey priv; + blsHashToSecretKey(&priv, seed.pointer(), seed.size()); + return priv; +} + +template +KeyType PrivateKeyShare(std::vector &kl, Id const &id) +{ + KeyType ret; + int32_t error = blsSecretKeyShare(&ret, kl.data(), kl.size(), &id); + + if (error != 0) + { + throw std::runtime_error("failed to generate private key share"); + } + + return ret; +} + +inline void AddKeys(PrivateKey &lhs, PrivateKey const &rhs) +{ + blsSecretKeyAdd(&lhs, &rhs); +} + +inline void AddKeys(PublicKey &lhs, PublicKey const &rhs) +{ + blsPublicKeyAdd(&lhs, &rhs); +} + +inline bool PublicKeyIsEqual(PublicKey const &pk1, PublicKey const &pk2) +{ + return blsPublicKeyIsEqual(&pk1, &pk2); +} + +inline PublicKey GetPublicKey(PrivateKey const &sk) +{ + PublicKey ret; + blsGetPublicKey(&ret, &sk); + return ret; +} + +inline PublicKey PublicKeyShare(PublicKeyList const &master_keys, Id const &id) +{ + PublicKey ret; + blsPublicKeyShare(&ret, master_keys.data(), master_keys.size(), &id); + return ret; +} + +inline Signature RecoverSignature(SignatureList const &sigs, IdList const &ids) +{ + Signature ret; + if (blsSignatureRecover(&ret, sigs.data(), ids.data(), sigs.size()) != 0) + { + throw std::runtime_error("Unable to recover signature"); + } + return ret; +} + +inline byte_array::ConstByteArray ToBinary(Signature const &sig) +{ + byte_array::ByteArray buffer{}; + buffer.Resize(1024); +#ifdef BLS_SWAP_G + size_t n = mclBnG2_getStr(buffer.char_pointer(), buffer.size(), &sig.v, 0); +#else + size_t n = mclBnG1_getStr(buffer.char_pointer(), buffer.size(), &sig.v, 0); +#endif + if (n == 0) + { + throw std::runtime_error("Signature:tgetStr"); + } + buffer.Resize(n); + + return {buffer}; +} + +} // namespace bls + +} // namespace crypto +} // namespace fetch + +template +void Serialize(T &stream, ::blsId const &id) +{ + stream.Allocate(sizeof(id)); + auto const *raw = reinterpret_cast(&id); + stream.WriteBytes(raw, sizeof(id)); +} + +template +void Deserialize(T &stream, ::blsId &id) +{ + auto *raw = reinterpret_cast(&id); + stream.ReadBytes(raw, sizeof(id)); +} + +template +void Serialize(T &stream, ::blsPublicKey const &public_key) +{ + stream.Allocate(sizeof(public_key)); + auto const *raw = reinterpret_cast(&public_key); + stream.WriteBytes(raw, sizeof(public_key)); +} + +template +void Deserialize(T &stream, ::blsPublicKey &public_key) +{ + auto *raw = reinterpret_cast(&public_key); + stream.ReadBytes(raw, sizeof(public_key)); +} + +template +void Serialize(T &stream, ::blsSecretKey const &secret_key) +{ + stream.Allocate(sizeof(secret_key)); + auto const *raw = reinterpret_cast(&secret_key); + stream.WriteBytes(raw, sizeof(secret_key)); +} + +template +void Deserialize(T &stream, ::blsSecretKey &secret_key) +{ + auto *raw = reinterpret_cast(&secret_key); + stream.ReadBytes(raw, sizeof(secret_key)); +} + +template +void Serialize(T &stream, ::blsSignature const &signature) +{ + stream.Allocate(sizeof(signature)); + auto const *raw = reinterpret_cast(&signature); + stream.WriteBytes(raw, sizeof(signature)); +} + +template +void Deserialize(T &stream, ::blsSignature &signature) +{ + auto *raw = reinterpret_cast(&signature); + stream.ReadBytes(raw, sizeof(signature)); +} diff --git a/libs/crypto/include/crypto/bls_dkg.hpp b/libs/crypto/include/crypto/bls_dkg.hpp new file mode 100644 index 0000000000..a957724165 --- /dev/null +++ b/libs/crypto/include/crypto/bls_dkg.hpp @@ -0,0 +1,111 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "crypto/bls_base.hpp" + +namespace fetch { +namespace crypto { + +namespace bls { +namespace dkg { +using VerificationVector = std::vector; +using ContributionVector = std::vector; +using ParticipantVector = std::vector; +struct Contribution +{ + VerificationVector verification; + ContributionVector contributions; +}; + +Contribution GenerateContribution(ParticipantVector const &participants, uint32_t threshold) +{ + Contribution ret; + bls::PrivateKeyList private_keys; + + for (uint32_t i = 0; i < threshold; ++i) + { + auto sk = bls::PrivateKeyByCSPRNG(); + private_keys.push_back(sk); + + auto pk = bls::PublicKeyFromPrivate(sk); + ret.verification.push_back(pk); + } + + for (auto &id : participants) + { + auto sk = bls::PrivateKeyShare(private_keys, id); + ret.contributions.push_back(sk); + } + + return ret; +} + +bls::PrivateKey AccumulateContributionShares(bls::PrivateKeyList list) +{ + if (list.size() == 0) + { + throw std::runtime_error("Cannot accumulate empty list"); + } + + uint64_t i = 0; + auto sum = list[i++]; + for (; i < list.size(); ++i) + { + bls::AddKeys(sum, list[i]); + } + + return sum; +} + +bool VerifyContributionShare(bls::Id id, bls::PrivateKey const &contribution, + bls::PublicKeyList const &pkl) +{ + bls::PublicKey pk1 = bls::PublicKeyShare(pkl, id); + bls::PublicKey pk2 = bls::GetPublicKey(contribution); + + return bls::PublicKeyIsEqual(pk1, pk2); +} + +VerificationVector AccumulateVerificationVectors(std::vector const &vectors) +{ + VerificationVector ret; + auto N = vectors.size(); + ret = vectors[0]; + for (uint64_t i = 1; i < N; ++i) + { + auto const &subvec = vectors[i]; + auto M = subvec.size(); + + if (M == 0) + { + throw std::runtime_error("vector length is zero in AccumulateVerificationVectors."); + } + + for (uint64_t j = 0; j < M; ++j) + { + bls::AddKeys(ret[j], subvec[j]); + } + } + return ret; +} + +} // namespace dkg +} // namespace bls +} // namespace crypto +} // namespace fetch diff --git a/libs/crypto/include/crypto/identity.hpp b/libs/crypto/include/crypto/identity.hpp index a86a8b6b31..0f2b8577d8 100644 --- a/libs/crypto/include/crypto/identity.hpp +++ b/libs/crypto/include/crypto/identity.hpp @@ -22,6 +22,7 @@ #include "core/byte_array/byte_array.hpp" #include "crypto/fnv.hpp" #include "crypto/openssl_common.hpp" +#include "crypto/signature_register.hpp" namespace fetch { namespace crypto { @@ -41,7 +42,7 @@ class Identity // Fully relying on caller that it will behave = will NOT modify value passed // (Const)ByteArray(s) - Identity(byte_array::ConstByteArray identity_parameters, byte_array::ConstByteArray identifier) + Identity(uint8_t identity_parameters, byte_array::ConstByteArray identifier) : identity_parameters_{std::move(identity_parameters)} , identifier_{std::move(identifier)} {} @@ -50,7 +51,7 @@ class Identity : identifier_{std::move(identifier)} {} - byte_array::ConstByteArray const ¶meters() const + uint8_t const ¶meters() const { return identity_parameters_; } @@ -65,15 +66,14 @@ class Identity identifier_ = ident; } - void SetParameters(byte_array::ConstByteArray const ¶ms) + void SetParameters(uint8_t p) { - identity_parameters_ = params; + identity_parameters_ = std::move(p); } operator bool() const { - return identity_parameters_ == edcsa_curve_type::sn && - identifier_.size() == edcsa_curve_type::publicKeySize; + return TestIdentityParameterSize(identity_parameters_, identifier_.size()); } static Identity CreateInvalid() @@ -103,12 +103,11 @@ class Identity void Clone() { - identifier_ = identifier_.Copy(); - identity_parameters_ = identity_parameters_.Copy(); + identifier_ = identifier_.Copy(); } private: - byte_array::ConstByteArray identity_parameters_{edcsa_curve_type::sn}; + uint8_t identity_parameters_{edcsa_curve_type::sn}; byte_array::ConstByteArray identifier_; }; @@ -129,7 +128,8 @@ T &Serialize(T &serializer, Identity const &data) template T &Deserialize(T &serializer, Identity &data) { - byte_array::ByteArray params, id; + uint8_t params; + byte_array::ByteArray id; serializer >> id; serializer >> params; diff --git a/libs/crypto/include/crypto/openssl_common.hpp b/libs/crypto/include/crypto/openssl_common.hpp index 5f2d25e687..8476547f03 100644 --- a/libs/crypto/include/crypto/openssl_common.hpp +++ b/libs/crypto/include/crypto/openssl_common.hpp @@ -20,6 +20,7 @@ #include "core/byte_array/byte_array.hpp" #include "core/byte_array/const_byte_array.hpp" #include "crypto/openssl_memory.hpp" +#include "crypto/signature_register.hpp" #include @@ -34,7 +35,7 @@ template struct ECDSACurve { static const int nid; - static const char *const sn; + static const uint8_t sn; static const std::size_t privateKeySize; static const std::size_t publicKeySize; static const std::size_t signatureSize; @@ -44,7 +45,7 @@ template const int ECDSACurve::nid = P_ECDSA_Curve_NID; template <> -const char *const ECDSACurve::sn; +const uint8_t ECDSACurve::sn; template <> const std::size_t ECDSACurve::privateKeySize; template <> diff --git a/libs/crypto/include/crypto/signature_register.hpp b/libs/crypto/include/crypto/signature_register.hpp new file mode 100644 index 0000000000..923ab03ef1 --- /dev/null +++ b/libs/crypto/include/crypto/signature_register.hpp @@ -0,0 +1,62 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace fetch { +namespace crypto { + +enum SignatureType : uint8_t +{ + SECP256K1_COMPRESSED = 0x02, + SECP256K1_COMPRESSED2 = 0x03, + SECP256K1_UNCOMPRESSED = 0x04, + BLS_BN256_UNCOMPRESSED = 0x20 +}; + +constexpr bool IdentityParameterTypeDefined(uint8_t x) +{ + switch (x) + { + case SECP256K1_COMPRESSED: + case SECP256K1_COMPRESSED2: + case SECP256K1_UNCOMPRESSED: + case BLS_BN256_UNCOMPRESSED: + return true; + } + + return false; +} + +constexpr bool TestIdentityParameterSize(uint8_t x, uint64_t s) +{ + switch (x) + { + case SECP256K1_COMPRESSED: + case SECP256K1_COMPRESSED2: + return s == 32; + case SECP256K1_UNCOMPRESSED: + return s == 64; + case BLS_BN256_UNCOMPRESSED: + return true; + } + + return false; +} + +} // namespace crypto +} // namespace fetch diff --git a/libs/crypto/include/fetch_bls.hpp b/libs/crypto/include/fetch_bls.hpp new file mode 100644 index 0000000000..c56d3202fa --- /dev/null +++ b/libs/crypto/include/fetch_bls.hpp @@ -0,0 +1,40 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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. +// +//------------------------------------------------------------------------------ + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wpedantic" +#endif + +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/libs/crypto/src/bls_base.cpp b/libs/crypto/src/bls_base.cpp new file mode 100644 index 0000000000..314425f7ad --- /dev/null +++ b/libs/crypto/src/bls_base.cpp @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "crypto/bls_base.hpp" + +#include + +namespace fetch { +namespace crypto { +namespace bls { +namespace { + +std::atomic g_initialised{false}; + +} // namespace + +/** + * Initialise the BLS library + */ +void Init() +{ + // determine if the library was previously initialised + bool const was_previously_initialised = g_initialised.exchange(true); + + if (!was_previously_initialised) + { + if (blsInit(E_MCLBN_CURVE_FP254BNB, MCLBN_COMPILED_TIME_VAR) != 0) + { + throw std::runtime_error("unable to initalize BLS."); + } + } +} + +} // namespace bls +} // namespace crypto +} // namespace fetch diff --git a/libs/crypto/src/openssl_common.cpp b/libs/crypto/src/openssl_common.cpp index 32d8cd85ed..9b46fbd3c8 100644 --- a/libs/crypto/src/openssl_common.cpp +++ b/libs/crypto/src/openssl_common.cpp @@ -17,13 +17,14 @@ //------------------------------------------------------------------------------ #include "crypto/openssl_common.hpp" +#include "crypto/signature_register.hpp" namespace fetch { namespace crypto { namespace openssl { template <> -char const *const ECDSACurve::sn = SN_secp256k1; +uint8_t const ECDSACurve::sn = SECP256K1_UNCOMPRESSED; template <> std::size_t const ECDSACurve::privateKeySize = 32; template <> diff --git a/libs/crypto/tests/gtests/bls_tests.cpp b/libs/crypto/tests/gtests/bls_tests.cpp new file mode 100644 index 0000000000..44f53e0938 --- /dev/null +++ b/libs/crypto/tests/gtests/bls_tests.cpp @@ -0,0 +1,225 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "crypto/bls_base.hpp" +#include "crypto/bls_dkg.hpp" + +#include "gtest/gtest.h" + +#include + +using namespace fetch::crypto; +using namespace fetch::byte_array; + +using SecretKeyArray = std::vector(); + +template +void ToBinary(std::ostream &stream, T const &value) +{ + auto const * raw = reinterpret_cast(&value); + ConstByteArray data{raw, sizeof(T)}; + + stream << data.ToBase64(); +} + +std::ostream &operator<<(std::ostream &stream, bls::PrivateKey const &private_key) +{ + ToBinary(stream, private_key); + return stream; +} + +std::ostream &operator<<(std::ostream &stream, bls::Id const &private_key) +{ + ToBinary(stream, private_key); + return stream; +} + +std::ostream &operator<<(std::ostream &stream, bls::PublicKey const &private_key) +{ + ToBinary(stream, private_key); + return stream; +} + +std::ostream &operator<<(std::ostream &stream, bls::Signature const &private_key) +{ + ToBinary(stream, private_key); + return stream; +} + +bls::Id GenerateId() +{ + auto const sk = bls::PrivateKeyByCSPRNG(); + bls::Id id; + id.v = sk.v; + + return id; +} + +TEST(BlsTests, SimpleRandomBeaconFlow) +{ + bls::Init(); + + auto dealer = bls::PrivateKeyByCSPRNG(); + auto dealer_pub = bls::PublicKeyFromPrivate(dealer); + + auto sk1 = bls::PrivateKeyByCSPRNG(); + auto sk2 = bls::PrivateKeyByCSPRNG(); + auto sk3 = bls::PrivateKeyByCSPRNG(); + auto sk4 = bls::PrivateKeyByCSPRNG(); + + std::cout << "SK1 " << sk1 << std::endl; + std::cout << "SK2 " << sk2 << std::endl; + std::cout << "SK3 " << sk3 << std::endl; + std::cout << "SK4 " << sk4 << std::endl; + + bls::Id id1 = GenerateId(); + bls::Id id2 = GenerateId(); + bls::Id id3 = GenerateId(); + bls::Id id4 = GenerateId(); + + std::cout << "ID1 " << id1 << std::endl; + std::cout << "ID2 " << id2 << std::endl; + std::cout << "ID3 " << id3 << std::endl; + std::cout << "ID4 " << id4 << std::endl; + + std::vector master_key(2); // threshold + master_key[0] = dealer; + master_key[1] = bls::PrivateKeyByCSPRNG(); + + auto share1 = bls::PrivateKeyShare(master_key, id1); + auto share2 = bls::PrivateKeyShare(master_key, id2); + auto share3 = bls::PrivateKeyShare(master_key, id3); + auto share4 = bls::PrivateKeyShare(master_key, id4); + + auto share_pub1 = bls::PublicKeyFromPrivate(share1); + auto share_pub2 = bls::PublicKeyFromPrivate(share2); + auto share_pub3 = bls::PublicKeyFromPrivate(share3); + auto share_pub4 = bls::PublicKeyFromPrivate(share4); + + ConstByteArray message = "hello my name is ed"; + + auto sig1 = bls::Sign(share1, message); + EXPECT_TRUE(bls::Verify(sig1, share_pub1, message)); + + auto sig2 = bls::Sign(share2, message); + EXPECT_TRUE(bls::Verify(sig2, share_pub2, message)); + + auto sig3 = bls::Sign(share3, message); + EXPECT_TRUE(bls::Verify(sig3, share_pub3, message)); + + auto sig4 = bls::Sign(share4, message); + EXPECT_TRUE(bls::Verify(sig4, share_pub4, message)); + + ConstByteArray reference_sig; + { + auto recovered_sig = bls::RecoverSignature({sig1, sig2, sig3}, {id1, id2, id3}); + reference_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + } + + { + auto recovered_sig = bls::RecoverSignature({sig2, sig4, sig3}, {id2, id4, id3}); + auto bin_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + EXPECT_EQ(reference_sig, bin_sig); + } + + { + auto recovered_sig = bls::RecoverSignature({sig1, sig4}, {id1, id4}); + auto bin_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + EXPECT_EQ(reference_sig, bin_sig); + } + + message = "hello my name is ed again"; + + sig1 = bls::Sign(share1, message); + EXPECT_TRUE(bls::Verify(sig1, share_pub1, message)); + + sig2 = bls::Sign(share2, message); + EXPECT_TRUE(bls::Verify(sig2, share_pub2, message)); + + sig3 = bls::Sign(share3, message); + EXPECT_TRUE(bls::Verify(sig3, share_pub3, message)); + + sig4 = bls::Sign(share4, message); + EXPECT_TRUE(bls::Verify(sig4, share_pub4, message)); + + { + auto recovered_sig = bls::RecoverSignature({sig1, sig2, sig3}, {id1, id2, id3}); + reference_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + } + + { + auto recovered_sig = bls::RecoverSignature({sig2, sig4, sig3}, {id2, id4, id3}); + auto bin_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + EXPECT_EQ(reference_sig, bin_sig); + } + + { + auto recovered_sig = bls::RecoverSignature({sig1, sig4}, {id1, id4}); + auto bin_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + EXPECT_EQ(reference_sig, bin_sig); + } + + message = "hello my name is ed again2"; + + sig1 = bls::Sign(share1, message); + EXPECT_TRUE(bls::Verify(sig1, share_pub1, message)); + + sig2 = bls::Sign(share2, message); + EXPECT_TRUE(bls::Verify(sig2, share_pub2, message)); + + sig3 = bls::Sign(share3, message); + EXPECT_TRUE(bls::Verify(sig3, share_pub3, message)); + + sig4 = bls::Sign(share4, message); + EXPECT_TRUE(bls::Verify(sig4, share_pub4, message)); + + { + auto recovered_sig = bls::RecoverSignature({sig1, sig2, sig3}, {id1, id2, id3}); + reference_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + } + + { + auto recovered_sig = bls::RecoverSignature({sig2, sig4, sig3}, {id2, id4, id3}); + auto bin_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + EXPECT_EQ(reference_sig, bin_sig); + } + + { + auto recovered_sig = bls::RecoverSignature({sig1, sig4}, {id1, id4}); + auto bin_sig = bls::ToBinary(recovered_sig); + + EXPECT_TRUE(bls::Verify(recovered_sig, dealer_pub, message)); + EXPECT_EQ(reference_sig, bin_sig); + } +} diff --git a/libs/crypto/tests/gtests/openssl_common_test.cpp b/libs/crypto/tests/gtests/openssl_common_test.cpp index e8d2c675a4..60c1374136 100644 --- a/libs/crypto/tests/gtests/openssl_common_test.cpp +++ b/libs/crypto/tests/gtests/openssl_common_test.cpp @@ -29,13 +29,13 @@ class ECDSACurveTest : public testing::Test { protected: template - void test_ECDSACurve(const char *const expected_sn, const std::size_t expected_privateKeySize, + void test_ECDSACurve(const uint8_t expected_sn, const std::size_t expected_privateKeySize, const std::size_t expected_publicKeySize, const std::size_t expected_signatureSize) { using ecdsa_curve_type = ECDSACurve; EXPECT_EQ(ecdsa_curve_type::nid, P_ECDSA_Curve_NID); - ASSERT_STREQ(expected_sn, ecdsa_curve_type::sn); + ASSERT_EQ(expected_sn, ecdsa_curve_type::sn); EXPECT_EQ(expected_privateKeySize, ecdsa_curve_type::privateKeySize); EXPECT_EQ(expected_publicKeySize, ecdsa_curve_type::publicKeySize); EXPECT_EQ(expected_signatureSize, ecdsa_curve_type::signatureSize); @@ -44,7 +44,7 @@ class ECDSACurveTest : public testing::Test TEST_F(ECDSACurveTest, test_ECDSACurve_for_NID_secp256k1) { - test_ECDSACurve(SN_secp256k1, 32, 64, 64); + test_ECDSACurve(fetch::crypto::SignatureType::SECP256K1_UNCOMPRESSED, 32, 64, 64); } class ECDSAAffineCoordinatesConversionTest : public testing::Test diff --git a/libs/dkg/CMakeLists.txt b/libs/dkg/CMakeLists.txt new file mode 100644 index 0000000000..0fff8db654 --- /dev/null +++ b/libs/dkg/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# F E T C H D K G L I B R A R Y +# +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) +project(fetch-dkg) + +# CMake Configuration +include(${FETCH_ROOT_CMAKE_DIR}/BuildTools.cmake) + +# Compiler Configuration +setup_compiler() + +# ------------------------------------------------------------------------------ +# Main Library Target +# ------------------------------------------------------------------------------ + +setup_library(fetch-dkg) +target_link_libraries(fetch-dkg + PUBLIC fetch-core + fetch-crypto + fetch-network + fetch-ledger) diff --git a/libs/dkg/include/dkg/dkg_rpc_protocol.hpp b/libs/dkg/include/dkg/dkg_rpc_protocol.hpp new file mode 100644 index 0000000000..e0b190311f --- /dev/null +++ b/libs/dkg/include/dkg/dkg_rpc_protocol.hpp @@ -0,0 +1,54 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "network/service/protocol.hpp" + +namespace fetch { +namespace dkg { + +class DkgService; + +/** + * The RPC protocol class for the DKG Service + */ +class DkgRpcProtocol : public service::Protocol +{ +public: + enum + { + REQUEST_SECRET, + SUBMIT_SIGNATURE, + }; + + // Construction / Destruction + explicit DkgRpcProtocol(DkgService &service); + DkgRpcProtocol(DkgRpcProtocol const &) = delete; + DkgRpcProtocol(DkgRpcProtocol &&) = delete; + ~DkgRpcProtocol() override = default; + + // Operators + DkgRpcProtocol &operator=(DkgRpcProtocol const &) = delete; + DkgRpcProtocol &operator=(DkgRpcProtocol &&) = delete; + +private: + DkgService &service_; +}; + +} // namespace dkg +} // namespace fetch diff --git a/libs/dkg/include/dkg/dkg_service.hpp b/libs/dkg/include/dkg/dkg_service.hpp new file mode 100644 index 0000000000..fc5cc862b0 --- /dev/null +++ b/libs/dkg/include/dkg/dkg_service.hpp @@ -0,0 +1,281 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "core/byte_array/const_byte_array.hpp" +#include "core/byte_array/decoders.hpp" +#include "core/containers/mapping.hpp" +#include "core/mutex.hpp" +#include "core/state_machine.hpp" +#include "crypto/bls_base.hpp" +#include "dkg/dkg_rpc_protocol.hpp" +#include "dkg/round.hpp" +#include "ledger/chain/address.hpp" +#include "ledger/consensus/entropy_generator_interface.hpp" +#include "network/muddle/rpc/client.hpp" +#include "network/muddle/rpc/server.hpp" + +#include +#include +#include +#include + +namespace fetch { +namespace muddle { + +class MuddleEndpoint; +class Subscription; + +} // namespace muddle + +namespace dkg { + +/** + * The DKG service is designed to provide the system with a reliable entropy source that can be + * integrated into the staking mechanism. + * + * The DKG will build a set of keys for for a given block period called an aeon. During this aeon + * signatures will be sent out from each participant on a round basis. These rounds roughly sync + * up with block intervals. However, it should be noted that in general the DKG will run ahead of + * the main chain. + * + * The following diagram outlines that basic state machine that is operating in the DKG service. + * + * ┌───────────────────────┐ + * │ │ + * │ Build Keys │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + * │ │ │ + * └───────────────────────┘ + * │ │ + * │ At the start + * ▼ of the next + * ┌───────────────────────┐ aeon + * │ │ │ + * │ Request Secret Key │ + * │ │ │ + * └───────────────────────┘ + * │ │ + * │ + * ▼ │ + * ┌───────────────────────┐ + * │ │ │ + * │ Wait for Secret Key │ + * │ │ │ + * └───────────────────────┘ + * │ │ + * │ + * ▼ │ + * ┌───────────────────────┐ + * │ │ │ + * │ Broadcast Signature │◀ ─ ─ ─ ─ ─ ┐ + * │ │ │ + * └───────────────────────┘ │ + * │ │ + * │ At the start + * ▼ of the next │ + * ┌───────────────────────┐ round + * │ │ │ + * │ Collect Signatures │ │ + * │ │ │ + * └───────────────────────┘ │ + * │ │ + * │ │ + * ▼ │ + * ┌───────────────────────┐ │ + * │ │ │ + * │ Complete │─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ + * │ │ + * └───────────────────────┘ + */ +class DkgService : public ledger::EntropyGeneratorInterface +{ +public: + enum class State + { + BUILD_AEON_KEYS, + REQUEST_SECRET_KEY, + WAIT_FOR_SECRET_KEY, + BROADCAST_SIGNATURE, + COLLECT_SIGNATURES, + COMPLETE, + }; + + using Endpoint = muddle::MuddleEndpoint; + using Digest = ledger::Digest; + using ConstByteArray = byte_array::ConstByteArray; + using MuddleAddress = ConstByteArray; + using CabinetMembers = std::unordered_set; + + // Construction / Destruction + explicit DkgService(Endpoint &endpoint, ConstByteArray address, ConstByteArray dealer_address); + DkgService(DkgService const &) = delete; + DkgService(DkgService &&) = delete; + ~DkgService() override = default; + + /// @name External Events + /// @{ + struct SecretKeyReq + { + bool success{false}; + crypto::bls::PrivateKey secret_share{}; + crypto::bls::PublicKey shared_public_key{}; + }; + SecretKeyReq RequestSecretKey(MuddleAddress const &address); + + void SubmitSignatureShare(uint64_t round, crypto::bls::Id const &id, + crypto::bls::PublicKey const &public_key, + crypto::bls::Signature const &signature); + /// @} + + /// @name Entropy Generator + /// @{ + Status GenerateEntropy(Digest block_digest, uint64_t block_number, uint64_t &entropy) override; + /// @} + + /// @name Helper Methods + /// @{ + std::weak_ptr GetWeakRunnable() + { + return state_machine_; + } + + void ResetCabinet(CabinetMembers cabinet, std::size_t threshold) + { + FETCH_LOCK(cabinet_lock_); + current_cabinet_ = std::move(cabinet); + current_threshold_ = threshold; + } + /// @} + + // Operators + DkgService &operator=(DkgService const &) = delete; + DkgService &operator=(DkgService &&) = delete; + +private: + using Address = ledger::Address; + using SubscriptionPtr = std::shared_ptr; + using AddressMapping = core::Mapping; + using EntropyHistory = std::unordered_map; + using CabinetIds = std::unordered_map; + using CabinetKeys = std::unordered_map; + using StateMachine = core::StateMachine; + using StateMachinePtr = std::shared_ptr; + using RpcProtocolPtr = std::unique_ptr; + using Promise = service::Promise; + using RMutex = std::recursive_mutex; + using Mutex = std::mutex; + using SignaturePtr = std::unique_ptr; + using SignatureMap = std::map; + using RoundMap = std::map; + using PrivateKey = crypto::bls::PrivateKey; + using PublicKey = crypto::bls::PublicKey; + using PublicKeyList = crypto::bls::PublicKeyList; + + struct Submission + { + uint64_t round; + crypto::bls::Id id; + crypto::bls::PublicKey public_key; + crypto::bls::Signature signature; + }; + + using SubmissionList = std::deque; + + /// @name State Handlers + /// @{ + State OnBuildAeonKeysState(); + State OnRequestSecretKeyState(); + State OnWaitForSecretKeyState(); + State OnBroadcastSignatureState(); + State OnCollectSignaturesState(); + State OnCompleteState(); + /// @} + + /// @name Utils + /// @{ + bool BuildAeonKeys(); + bool GetSignaturePayload(uint64_t round, ConstByteArray &payload); + RoundPtr LookupRound(uint64_t round, bool create = false); + /// @} + + ConstByteArray const address_; ///< Our muddle address + crypto::bls::Id const id_; ///< Our BLS ID (derived from the muddle address) + ConstByteArray const dealer_address_; ///< The address of the dealer + bool const is_dealer_; ///< Flag to signal if we are the dealer + Endpoint & endpoint_; ///< The muddle endpoint to communicate on + muddle::rpc::Server rpc_server_; ///< The services' RPC server + muddle::rpc::Client rpc_client_; ///< The services' RPC client + RpcProtocolPtr rpc_proto_; ///< The services RPC protocol + StateMachinePtr state_machine_; ///< The service state machine + + /// @name State Machine Data + /// @{ + Promise pending_promise_; ///< The cached pending promise + PrivateKey aeon_secret_share_{}; ///< The current secret share for the aeon + PublicKey aeon_share_public_key_{}; /// < The shared public key for the aeon + PublicKey aeon_public_key_{}; ///< The public key for our secret share + /// @} + + /// @name Cabinet / Aeon Data + /// @{ + mutable RMutex cabinet_lock_{}; // Priority 1. + std::size_t current_threshold_{1}; ///< The current threshold for the aeon + CabinetMembers current_cabinet_{}; ///< The set of muddle addresses of the cabinet + /// @} + + /// @name Dealer Specific Data + /// @{ + mutable RMutex dealer_lock_; // Priority 2. + PublicKey shared_public_key_; ///< The shared public key for the aeon + CabinetKeys current_cabinet_secrets_{}; ///< The map of address to secrets + /// @} + + /// @name Round Data + /// @{ + mutable RMutex round_lock_{}; // Priority 3. + SubmissionList pending_signatures_{}; ///< The queue of pending signatures + std::atomic earliest_completed_round_{0}; ///< The round idx for the next entropy req + std::atomic current_round_{0}; ///< The current round being generated + RoundMap rounds_{}; ///< The map of round data + /// @} +}; + +template +void Serialize(T &stream, DkgService::SecretKeyReq const &req) +{ + stream << req.success; + + if (req.success) + { + stream << req.secret_share << req.shared_public_key; + } +} + +template +void Deserialize(T &stream, DkgService::SecretKeyReq &req) +{ + stream >> req.success; + + if (req.success) + { + stream >> req.secret_share >> req.shared_public_key; + } +} + +} // namespace dkg +} // namespace fetch diff --git a/libs/dkg/include/dkg/round.hpp b/libs/dkg/include/dkg/round.hpp new file mode 100644 index 0000000000..d636beaeb6 --- /dev/null +++ b/libs/dkg/include/dkg/round.hpp @@ -0,0 +1,118 @@ +#pragma once +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "core/byte_array/const_byte_array.hpp" +#include "core/mutex.hpp" +#include "crypto/bls_base.hpp" + +#include +#include + +namespace fetch { +namespace dkg { + +/** + * The round structure contains all the information that is required for a single round of entropy + * generation. + */ +class Round +{ +public: + using ConstByteArray = byte_array::ConstByteArray; + + // Construction / Destruction + explicit Round(uint64_t round); + Round(Round const &) = delete; + Round(Round &&) = delete; + ~Round() = default; + + uint64_t round() const; + crypto::bls::Signature const &round_signature() const + { + return round_signature_; + } + + void AddShare(crypto::bls::Id const &id, crypto::bls::Signature const &sig); + bool HasSignature() const; + std::size_t GetNumShares() const; + uint64_t GetEntropy() const; + void SetSignature(crypto::bls::Signature const &sig); + ConstByteArray GetRoundEntropy() const; + void RecoverSignature(); + + // Operators + Round &operator=(Round const &) = delete; + Round &operator=(Round &&) = delete; + +private: + uint64_t const round_; + mutable std::mutex lock_{}; + crypto::bls::IdList sig_ids_{}; + crypto::bls::SignatureList sig_shares_{}; + crypto::bls::Signature round_signature_{}; + byte_array::ConstByteArray round_entropy_{}; + + std::atomic num_shares_{0}; + std::atomic has_signature_{}; +}; + +/** + * Construct the DKG round with a given id + * + * @param round The round id + */ +inline Round::Round(uint64_t round) + : round_{round} +{} + +/** + * Get the current round id + * + * @return The round id + */ +inline uint64_t Round::round() const +{ + return round_; +} + +/** + * Checks to see if the round has a signature (and therefore is complete) + * + * @return true if signature is present, otherwise false + */ +inline bool Round::HasSignature() const +{ + FETCH_LOCK(lock_); + return has_signature_; +} + +/** + * Gets the total number of signature shares which has be cached for this DKG round + * + * @return The number of shares present + */ +inline std::size_t Round::GetNumShares() const +{ + return num_shares_; +} + +using RoundPtr = std::shared_ptr; + +} // namespace dkg +} // namespace fetch diff --git a/libs/dkg/src/dkg_rpc_protocol.cpp b/libs/dkg/src/dkg_rpc_protocol.cpp new file mode 100644 index 0000000000..a241d75291 --- /dev/null +++ b/libs/dkg/src/dkg_rpc_protocol.cpp @@ -0,0 +1,33 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "dkg/dkg_rpc_protocol.hpp" +#include "dkg/dkg_service.hpp" + +namespace fetch { +namespace dkg { + +DkgRpcProtocol::DkgRpcProtocol(DkgService &service) + : service_{service} +{ + Expose(REQUEST_SECRET, &service_, &DkgService::RequestSecretKey); + Expose(SUBMIT_SIGNATURE, &service_, &DkgService::SubmitSignatureShare); +} + +} // namespace dkg +} // namespace fetch diff --git a/libs/dkg/src/dkg_service.cpp b/libs/dkg/src/dkg_service.cpp new file mode 100644 index 0000000000..2402ed09be --- /dev/null +++ b/libs/dkg/src/dkg_service.cpp @@ -0,0 +1,598 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "core/logging.hpp" +#include "core/serializers/byte_array.hpp" +#include "core/serializers/byte_array_buffer.hpp" +#include "core/service_ids.hpp" +#include "crypto/bls_dkg.hpp" +#include "crypto/sha256.hpp" +#include "dkg/dkg_service.hpp" +#include "network/muddle/muddle_endpoint.hpp" +#include "network/muddle/packet.hpp" +#include "network/muddle/subscription.hpp" + +#include + +namespace fetch { +namespace dkg { +namespace { + +using namespace std::chrono_literals; + +using byte_array::ConstByteArray; +using serializers::ByteArrayBuffer; + +using MuddleAddress = muddle::Packet::Address; +using State = DkgService::State; +using PromiseState = service::PromiseState; + +constexpr char const *LOGGING_NAME = "DkgService"; +constexpr uint64_t READ_AHEAD = 3; + +const ConstByteArray GENESIS_PAYLOAD = "=~=~ Genesis ~=~="; + +/** + * Creates a BLS ID from a specified Muddle Address + * + * @param address The input muddle address + * @return The generated BLS ID + */ +crypto::bls::Id CreateIdFromAddress(ConstByteArray const &address) +{ + auto const seed = crypto::bls::HashToPrivateKey(address); + return crypto::bls::Id{seed.v}; +} + +/** + * Converts the state enum to a string + * + * @param state The state to convert + * @return The string representation for the state + */ +char const *ToString(DkgService::State state) +{ + char const *text = "unknown"; + + switch (state) + { + case DkgService::State::BUILD_AEON_KEYS: + text = "Build Aeon Keys"; + break; + case DkgService::State::REQUEST_SECRET_KEY: + text = "Request Secret Key"; + break; + case DkgService::State::WAIT_FOR_SECRET_KEY: + text = "Wait for Secret Key"; + break; + case DkgService::State::BROADCAST_SIGNATURE: + text = "Broadcast Signature"; + break; + case DkgService::State::COLLECT_SIGNATURES: + text = "Collect Signatures"; + break; + case DkgService::State::COMPLETE: + text = "Complete"; + break; + } + + return text; +} + +} // namespace + +/** + * Creates a instance of the DKG Service + * + * @param endpoint The muddle endpoint to communicate on + * @param address The muddle endpoint address + * @param dealer_address The dealer address + */ +DkgService::DkgService(Endpoint &endpoint, ConstByteArray address, ConstByteArray dealer_address) + : address_{std::move(address)} + , id_{CreateIdFromAddress(address_)} + , dealer_address_{std::move(dealer_address)} + , is_dealer_{address_ == dealer_address_} + , endpoint_{endpoint} + , rpc_server_{endpoint_, SERVICE_DKG, CHANNEL_RPC} + , rpc_client_{"dkg", endpoint_, SERVICE_DKG, CHANNEL_RPC} + , state_machine_{std::make_shared("dkg", State::BUILD_AEON_KEYS, ToString)} +{ + // RPC server registration + rpc_proto_ = std::make_unique(*this); + rpc_server_.Add(RPC_DKG_BEACON, rpc_proto_.get()); + + // configure the state handlers + // clang-format off + state_machine_->RegisterHandler(State::BUILD_AEON_KEYS, this, &DkgService::OnBuildAeonKeysState); + state_machine_->RegisterHandler(State::REQUEST_SECRET_KEY, this, &DkgService::OnRequestSecretKeyState); + state_machine_->RegisterHandler(State::WAIT_FOR_SECRET_KEY, this, &DkgService::OnWaitForSecretKeyState); + state_machine_->RegisterHandler(State::BROADCAST_SIGNATURE, this, &DkgService::OnBroadcastSignatureState); + state_machine_->RegisterHandler(State::COLLECT_SIGNATURES, this, &DkgService::OnCollectSignaturesState); + state_machine_->RegisterHandler(State::COMPLETE, this, &DkgService::OnCompleteState); + // clang-format on +} + +/** + * RPC Handler: Handler for client secret request + * + * @param address The muddle address of the requester + * @return The response structure + */ +DkgService::SecretKeyReq DkgService::RequestSecretKey(MuddleAddress const &address) +{ + SecretKeyReq req{}; + + FETCH_LOCK(cabinet_lock_); + FETCH_LOCK(dealer_lock_); + + auto it = current_cabinet_secrets_.find(address); + if (it != current_cabinet_secrets_.end()) + { + FETCH_LOG_INFO(LOGGING_NAME, "Node: ", address.ToBase64(), " requested secret share"); + + // populate the request + req.success = true; + req.secret_share = it->second; + req.shared_public_key = shared_public_key_; + } + else + { + FETCH_LOG_WARN(LOGGING_NAME, "Failed to provide node: ", address.ToBase64(), + " with secret share"); + } + + return req; +} + +/** + * RPC Handler: Signature submission + * + * @param round The current round number + * @param id The id of the issuer + * @param public_key The public key for signature verification + * @param signature The signature to be submitted + */ +void DkgService::SubmitSignatureShare(uint64_t round, crypto::bls::Id const &id, + crypto::bls::PublicKey const &public_key, + crypto::bls::Signature const &signature) +{ + FETCH_LOG_TRACE(LOGGING_NAME, "Submit of signature for round ", round); + + FETCH_LOCK(round_lock_); + pending_signatures_.emplace_back(Submission{round, id, public_key, signature}); +} + +/** + * Generate entropy for a given block, identified by digest and block number + * + * @param block_digest The block digest + * @param block_number The block number + * @param entropy The output entropy value + * @return The associated status result for the operation + */ +DkgService::Status DkgService::GenerateEntropy(Digest block_digest, uint64_t block_number, + uint64_t &entropy) +{ + FETCH_UNUSED(block_digest); + + Status status{Status::NOT_READY}; + + // lookup the round + auto const round = LookupRound(block_number); + if (round && round->HasSignature()) + { + entropy = round->GetEntropy(); + status = Status::OK; + + // signal that the round has been consumed + ++earliest_completed_round_; + } + else + { + FETCH_LOG_ERROR(LOGGING_NAME, + "Trying to generate entropy ahead in time! block_number: ", block_number); + } + + return status; +} + +/** + * State Handler for BUILD_AEON_KEYS + * + * @return The next state to progress to + */ +State DkgService::OnBuildAeonKeysState() +{ + if (is_dealer_) + { + BuildAeonKeys(); + + // since we are the dealer we do not need to request a signature from ourselves + return State::BROADCAST_SIGNATURE; + } + + return State::REQUEST_SECRET_KEY; +} + +/** + * State Handler for REQUEST_SECRET_KEY + * + * @return The next state to progress to + */ +State DkgService::OnRequestSecretKeyState() +{ + // request from the beacon for the secret key + pending_promise_ = rpc_client_.CallSpecificAddress(dealer_address_, RPC_DKG_BEACON, + DkgRpcProtocol::REQUEST_SECRET, address_); + + return State::WAIT_FOR_SECRET_KEY; +} + +/** + * State Handler for WAIT_FOR_SECRET_KEY + * + * @return The next state to progress to + */ +State DkgService::OnWaitForSecretKeyState() +{ + State next_state{State::WAIT_FOR_SECRET_KEY}; + + FETCH_LOG_DEBUG(LOGGING_NAME, "State: Wait for secret"); + + bool waiting{true}; + + if (pending_promise_) + { + auto const current_state = pending_promise_->state(); + + switch (current_state) + { + case PromiseState::WAITING: + break; + case PromiseState::SUCCESS: + { + auto const response = pending_promise_->As(); + + if (response.success) + { + next_state = State::BROADCAST_SIGNATURE; + waiting = false; + aeon_secret_share_ = response.secret_share; + aeon_share_public_key_ = crypto::bls::GetPublicKey(aeon_secret_share_); + aeon_public_key_ = response.shared_public_key; + pending_promise_.reset(); + } + else + { + FETCH_LOG_INFO(LOGGING_NAME, "Response unsuccessful, retrying"); + next_state = State::REQUEST_SECRET_KEY; + } + + break; + } + case PromiseState::FAILED: + case PromiseState::TIMEDOUT: + FETCH_LOG_INFO(LOGGING_NAME, "Response timed out, retrying"); + next_state = State::REQUEST_SECRET_KEY; + break; + } + } + + if (waiting) + { + state_machine_->Delay(500ms); + } + + return next_state; +} + +/** + * State Handler for BROADCAST_SIGNATURE + * + * @return The next state to progress to + */ +State DkgService::OnBroadcastSignatureState() +{ + State next_state{State::COLLECT_SIGNATURES}; + + auto const this_round = current_round_.load(); + + FETCH_LOG_DEBUG(LOGGING_NAME, "OnBroadcastSignatureState round: ", this_round); + + // lookup / determine the payload we are expecting with the message + ConstByteArray payload{}; + if (this_round == 0) + { + payload = GENESIS_PAYLOAD; + } + else + { + if (!GetSignaturePayload(this_round - 1u, payload)) + { + FETCH_LOG_CRITICAL(LOGGING_NAME, + "Failed to lookup previous rounds signature data for broadcast"); + state_machine_->Delay(500ms); + return State::BROADCAST_SIGNATURE; // keep in a loop + } + } + + FETCH_LOG_DEBUG(LOGGING_NAME, "Payload: ", payload.ToBase64(), " (round: ", this_round, ")"); + + // create the signature + auto signature = crypto::bls::Sign(aeon_secret_share_, payload); + + FETCH_LOCK(cabinet_lock_); + for (auto const &member : current_cabinet_) + { + if (member == address_) + { + // we do not need to RPC call to ourselves we can simply provide the signature submission + FETCH_LOCK(round_lock_); + pending_signatures_.emplace_back( + Submission{this_round, id_, aeon_share_public_key_, signature}); + } + else + { + FETCH_LOG_TRACE(LOGGING_NAME, "Submitting Signature to: ", member.ToBase64()); + + // submit the signature to the cabinet member + rpc_client_.CallSpecificAddress(member, RPC_DKG_BEACON, DkgRpcProtocol::SUBMIT_SIGNATURE, + current_round_.load(), id_, aeon_share_public_key_, + signature); + } + } + + return next_state; +} + +/** + * State Handler for COLLECT_SIGNATURES + * + * @return The next state to progress to + */ +State DkgService::OnCollectSignaturesState() +{ + State next_state{State::COLLECT_SIGNATURES}; + + FETCH_LOCK(round_lock_); + + auto const this_round = current_round_.load(); + + FETCH_LOG_DEBUG(LOGGING_NAME, "OnCollectSignaturesState round: ", this_round); + + // Step 1. Process the signature submission pool + + // lookup / determine the payload we are expecting with the message + ConstByteArray payload; + if (this_round == 0) + { + payload = GENESIS_PAYLOAD; + } + else + { + if (!GetSignaturePayload(this_round - 1u, payload)) + { + FETCH_LOG_CRITICAL(LOGGING_NAME, "Failed to lookup previous rounds signature data"); + state_machine_->Delay(500ms); + return State::COLLECT_SIGNATURES; // keep in a loop + } + } + + // lookup the requesting round + auto const round = LookupRound(this_round, true); + assert(static_cast(round)); + + bool updates{false}; + + // process the pending signature submissions + auto it = pending_signatures_.begin(); + while (it != pending_signatures_.end()) + { + if (it->round <= this_round) + { + if (it->round == this_round) + { + // verify the signature + if (!crypto::bls::Verify(it->signature, it->public_key, payload)) + { + FETCH_LOG_ERROR(LOGGING_NAME, "Failed to very signature submision. Discarding"); + } + else + { + // successful verified this signature share - add it to the round + round->AddShare(it->id, it->signature); + updates = true; + } + } + + // this message has expired so we no longer + it = pending_signatures_.erase(it); + } + else + { + // move on to the next one + ++it; + } + } + + // Step 2. Determine if we have completed any signatures + if (!round->HasSignature() && round->GetNumShares() >= current_threshold_) + { + // recover the complete signature + round->RecoverSignature(); + + // verify that the signature is correct + if (!crypto::bls::Verify(round->round_signature(), aeon_public_key_, payload)) + { + FETCH_LOG_CRITICAL(LOGGING_NAME, "Failed to lookup verify signature"); + state_machine_->Delay(500ms); + return State::COLLECT_SIGNATURES; // keep in a loop + } + + FETCH_LOG_INFO(LOGGING_NAME, "Beacon: ", round->GetRoundEntropy().ToBase64(), + " round: ", round->round()); + + // have completed this iteration now + ++current_round_; + + next_state = State::COMPLETE; + } + + // if there have been no updates on this iteration, wait for a period of time + if (!updates) + { + state_machine_->Delay(500ms); + } + + return next_state; +} + +/** + * State Handler for COMPLETE + * + * The complete state is used as an idling state for the FSM + * + * @return The next state to progress to + */ +State DkgService::OnCompleteState() +{ + FETCH_LOG_DEBUG(LOGGING_NAME, "State: Complete round: ", requesting_iteration_.load(), + " read: ", current_iteration_.load()); + + // calculate the current number of signatures ahead + if (current_round_ <= earliest_completed_round_) + { + return State::BROADCAST_SIGNATURE; + } + else + { + uint64_t const delta = current_round_ - earliest_completed_round_; + if (delta < READ_AHEAD) + { + return State::BROADCAST_SIGNATURE; + } + } + + // TODO(issue 1285): Improve resource usage here for round cache + state_machine_->Delay(500ms); + + return State::COMPLETE; +} + +/** + * Builds a new set of keys for the aeon + * + * @return true if successful, otherwise false + */ +bool DkgService::BuildAeonKeys() +{ + FETCH_LOG_DEBUG(LOGGING_NAME, "Build new aeons key shares"); + + FETCH_LOCK(cabinet_lock_); + FETCH_LOCK(dealer_lock_); + + // generate the master key + crypto::bls::PrivateKeyList master_key(current_threshold_); + for (auto &key : master_key) + { + key = crypto::bls::PrivateKeyByCSPRNG(); + } + + // generate the corresponding shared public key + shared_public_key_ = crypto::bls::GetPublicKey(master_key[0]); + + // build a secret share for each of the + current_cabinet_secrets_.clear(); + for (auto const &member : current_cabinet_) + { + // use the unique muddle address to form the id + auto const id = CreateIdFromAddress(member); + + // create the secret share based on the member id + auto const secret_share = crypto::bls::PrivateKeyShare(master_key, id); + + if (member == address_) + { + aeon_secret_share_ = secret_share; + aeon_share_public_key_ = crypto::bls::GetPublicKey(aeon_secret_share_); + aeon_public_key_ = shared_public_key_; + } + + // update the cabinet map + current_cabinet_secrets_[member] = secret_share; + } + + return true; +} + +/** + * Gets the payload to be signed for a specified round interval + * + * @param round The round being queried + * @param payload The output payload to be populated + * @return true if successful, otherwise false + */ +bool DkgService::GetSignaturePayload(uint64_t round, ConstByteArray &payload) +{ + bool success{false}; + + auto const round_info = LookupRound(round); + if (round_info && round_info->HasSignature()) + { + payload = round_info->GetRoundEntropy(); + success = true; + } + + return success; +} + +/** + * Lookup the round information for a specified round index + * + * @param round The round index being requested + * @param create Flag to signal if a round should be created if it doesn't exist + * @return If successful the requested round object, otherwise a nullptr + */ +RoundPtr DkgService::LookupRound(uint64_t round, bool create) +{ + RoundPtr round_ptr{}; + FETCH_LOCK(round_lock_); + + auto it = rounds_.find(round); + if (it == rounds_.end()) + { + if (create) + { + // create the new round + round_ptr = std::make_shared(round); + + // store it in the map + rounds_[round] = round_ptr; + } + } + else + { + round_ptr = it->second; + } + + return round_ptr; +} + +} // namespace dkg +} // namespace fetch diff --git a/libs/dkg/src/round.cpp b/libs/dkg/src/round.cpp new file mode 100644 index 0000000000..839112b4ea --- /dev/null +++ b/libs/dkg/src/round.cpp @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2018-2019 Fetch.AI Limited +// +// 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 "core/mutex.hpp" +#include "crypto/hash.hpp" +#include "crypto/sha256.hpp" +#include "dkg/round.hpp" + +#include + +static bool operator==(::blsId const &a, ::blsId const &b) +{ + bool equal{false}; + + if (&a == &b) + { + equal = true; + } + else + { + equal = (0 == std::memcmp(&a, &b, sizeof(::blsId))); + } + + return equal; +} + +namespace fetch { +namespace dkg { + +void Round::AddShare(crypto::bls::Id const &id, crypto::bls::Signature const &sig) +{ + FETCH_LOCK(lock_); + + // ensure no duplicates are present + if (std::find(sig_ids_.begin(), sig_ids_.end(), id) == sig_ids_.end()) + { + sig_ids_.push_back(id); + sig_shares_.push_back(sig); + ++num_shares_; + } +} + +uint64_t Round::GetEntropy() const +{ + FETCH_LOCK(lock_); + return *reinterpret_cast(round_entropy_.pointer()); +} + +void Round::SetSignature(crypto::bls::Signature const &sig) +{ + FETCH_LOCK(lock_); + round_signature_ = sig; +} + +byte_array::ConstByteArray Round::GetRoundEntropy() const +{ + FETCH_LOCK(lock_); + return round_entropy_; +} + +void Round::RecoverSignature() +{ + FETCH_LOCK(lock_); + round_signature_ = crypto::bls::RecoverSignature(sig_shares_, sig_ids_); + has_signature_ = true; + round_entropy_ = crypto::Hash(crypto::bls::ToBinary(round_signature_)); +} + +} // namespace dkg +} // namespace fetch diff --git a/libs/ledger/CMakeLists.txt b/libs/ledger/CMakeLists.txt index 421281352e..b6ceb44f00 100644 --- a/libs/ledger/CMakeLists.txt +++ b/libs/ledger/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries(fetch-ledger fetch-vm-modules fetch-metrics fetch-moment + fetch-dkg vendor-msgpack) add_test_target() diff --git a/libs/ledger/include/ledger/consensus/entropy_generator_interface.hpp b/libs/ledger/include/ledger/consensus/entropy_generator_interface.hpp index 01e95f42ab..184620d9e9 100644 --- a/libs/ledger/include/ledger/consensus/entropy_generator_interface.hpp +++ b/libs/ledger/include/ledger/consensus/entropy_generator_interface.hpp @@ -29,9 +29,16 @@ class EntropyGeneratorInterface EntropyGeneratorInterface() = default; virtual ~EntropyGeneratorInterface() = default; + enum class Status + { + OK, + NOT_READY, + FAILED + }; + /// @name Entropy Generator /// @{ - virtual uint64_t GenerateEntropy(Digest block_digest, uint64_t block_number) = 0; + virtual Status GenerateEntropy(Digest block_digest, uint64_t block_number, uint64_t &entropy) = 0; /// @} }; diff --git a/libs/ledger/include/ledger/consensus/naive_entropy_generator.hpp b/libs/ledger/include/ledger/consensus/naive_entropy_generator.hpp index 1e41e54874..9622a30c41 100644 --- a/libs/ledger/include/ledger/consensus/naive_entropy_generator.hpp +++ b/libs/ledger/include/ledger/consensus/naive_entropy_generator.hpp @@ -39,7 +39,7 @@ class NaiveEntropyGenerator : public EntropyGeneratorInterface /// @name Entropy Generator Interface /// @{ - uint64_t GenerateEntropy(Digest block_digest, uint64_t block_number) override; + Status GenerateEntropy(Digest block_digest, uint64_t block_number, uint64_t &entropy) override; /// @} }; diff --git a/libs/ledger/include/ledger/consensus/stake_manager.hpp b/libs/ledger/include/ledger/consensus/stake_manager.hpp index e4eda228df..850f5e0019 100644 --- a/libs/ledger/include/ledger/consensus/stake_manager.hpp +++ b/libs/ledger/include/ledger/consensus/stake_manager.hpp @@ -49,6 +49,11 @@ class StakeManager final : public StakeManagerInterface bool ValidMinerForBlock(Block const &previous, Address const &address) override; /// @} + void UpdateEntropy(EntropyGeneratorInterface &entropy) + { + entropy_ = &entropy; + } + // Accessors StakeUpdateQueue & update_queue(); StakeUpdateQueue const &update_queue() const; @@ -70,17 +75,20 @@ class StakeManager final : public StakeManagerInterface using BlockIndex = uint64_t; using StakeSnapshotPtr = std::shared_ptr; using StakeHistory = std::map; + using EntropyCache = std::map; StakeSnapshotPtr LookupStakeSnapshot(BlockIndex block); void ResetInternal(StakeSnapshotPtr &&snapshot, std::size_t committee_size); + bool LookupEntropy(Block const &block, uint64_t &entropy); // Config & Components std::size_t committee_size_{0}; ///< The "static" size of the committee - EntropyGeneratorInterface &entropy_; ///< The reference to entropy module + EntropyGeneratorInterface *entropy_{nullptr}; ///< The reference to entropy module StakeUpdateQueue update_queue_; ///< The update queue of events StakeHistory history_{}; ///< Cache of historical snapshots StakeSnapshotPtr current_{}; ///< Most recent snapshot BlockIndex current_block_index_{0}; ///< Block index of most recent snapshot + EntropyCache entropy_cache_{}; }; inline std::size_t StakeManager::committee_size() const diff --git a/libs/ledger/include/ledger/genesis_loading/genesis_file_creator.hpp b/libs/ledger/include/ledger/genesis_loading/genesis_file_creator.hpp index 25a3a1ac90..772d280818 100644 --- a/libs/ledger/include/ledger/genesis_loading/genesis_file_creator.hpp +++ b/libs/ledger/include/ledger/genesis_loading/genesis_file_creator.hpp @@ -27,6 +27,10 @@ namespace variant { class Variant; } +namespace dkg { +class DkgService; +} + namespace ledger { class StakeManager; @@ -38,7 +42,7 @@ class GenesisFileCreator public: // Construction / Destruction GenesisFileCreator(BlockCoordinator &block_coordinator, StorageUnitInterface &storage_unit, - StakeManager *stake_manager); + StakeManager *stake_manager, dkg::DkgService *dkg); GenesisFileCreator(GenesisFileCreator const &) = delete; GenesisFileCreator(GenesisFileCreator &&) = delete; ~GenesisFileCreator() = default; @@ -55,18 +59,21 @@ class GenesisFileCreator void DumpStake(variant::Variant &object); void LoadState(variant::Variant const &object); void LoadStake(variant::Variant const &object); + void LoadDKG(variant::Variant const &object); BlockCoordinator & block_coordinator_; StorageUnitInterface &storage_unit_; StakeManager * stake_manager_{nullptr}; + dkg::DkgService * dkg_{nullptr}; }; inline GenesisFileCreator::GenesisFileCreator(BlockCoordinator & block_coordinator, StorageUnitInterface &storage_unit, - StakeManager * stake_manager) + StakeManager *stake_manager, dkg::DkgService *dkg) : block_coordinator_{block_coordinator} , storage_unit_{storage_unit} , stake_manager_{stake_manager} + , dkg_{dkg} {} } // namespace ledger diff --git a/libs/ledger/src/chain/block_coordinator.cpp b/libs/ledger/src/chain/block_coordinator.cpp index b0adb2b23f..f7c1b564cb 100644 --- a/libs/ledger/src/chain/block_coordinator.cpp +++ b/libs/ledger/src/chain/block_coordinator.cpp @@ -956,8 +956,9 @@ BlockCoordinator::State BlockCoordinator::OnTransmitBlock() // ensure that the main chain is aware of the block if (BlockStatus::ADDED == chain_.AddBlock(*next_block_)) { - FETCH_LOG_INFO(LOGGING_NAME, "Generating new block: 0x", next_block_->body.hash.ToHex(), - " txs: ", next_block_->GetTransactionCount()); + FETCH_LOG_INFO(LOGGING_NAME, "Broadcasting new block: 0x", next_block_->body.hash.ToHex(), + " txs: ", next_block_->GetTransactionCount(), + " number: ", next_block_->body.block_number); // mark this blocks transactions as being executed UpdateTxStatus(*next_block_); diff --git a/libs/ledger/src/consensus/naive_entropy_generator.cpp b/libs/ledger/src/consensus/naive_entropy_generator.cpp index 5659a756a7..3f76d93a92 100644 --- a/libs/ledger/src/consensus/naive_entropy_generator.cpp +++ b/libs/ledger/src/consensus/naive_entropy_generator.cpp @@ -24,6 +24,8 @@ namespace fetch { namespace ledger { +using Status = NaiveEntropyGenerator::Status; + /** * Generate Entropy for a specified block period * @@ -31,7 +33,8 @@ namespace ledger { * @param block_number The block number * @return The generated entropy */ -uint64_t ledger::NaiveEntropyGenerator::GenerateEntropy(Digest block_digest, uint64_t block_number) +Status NaiveEntropyGenerator::GenerateEntropy(Digest block_digest, uint64_t block_number, + uint64_t &entropy) { FETCH_UNUSED(block_number); @@ -42,8 +45,9 @@ uint64_t ledger::NaiveEntropyGenerator::GenerateEntropy(Digest block_digest, uin } auto const &digest_ref = block_digest; - auto const *entropy = reinterpret_cast(digest_ref.pointer()); - return *entropy; + entropy = *reinterpret_cast(digest_ref.pointer()); + + return Status::OK; } } // namespace ledger diff --git a/libs/ledger/src/consensus/stake_manager.cpp b/libs/ledger/src/consensus/stake_manager.cpp index f5cb376ea7..b82d7268c4 100644 --- a/libs/ledger/src/consensus/stake_manager.cpp +++ b/libs/ledger/src/consensus/stake_manager.cpp @@ -44,7 +44,7 @@ std::size_t SafeDecrement(std::size_t value, std::size_t decrement) } // namespace StakeManager::StakeManager(EntropyGeneratorInterface &entropy) - : entropy_{entropy} + : entropy_{&entropy} {} void StakeManager::UpdateCurrentBlock(Block const ¤t) @@ -86,7 +86,13 @@ StakeManager::CommitteePtr StakeManager::GetCommittee(Block const &previous) assert(static_cast(current_)); // generate the entropy for the previous block - auto const entropy = entropy_.GenerateEntropy(previous.body.hash, previous.body.block_number); + uint64_t entropy{0}; + if (!LookupEntropy(previous, entropy)) + { + FETCH_LOG_WARN(LOGGING_NAME, "Unable to lookup committee for ", previous.body.block_number, + " (entropy not ready)"); + return {}; + } // lookup the snapshot associated auto snapshot = LookupStakeSnapshot(previous.body.block_number); @@ -176,6 +182,37 @@ void StakeManager::ResetInternal(StakeSnapshotPtr &&snapshot, std::size_t commit current_block_index_ = 0; } +bool StakeManager::LookupEntropy(Block const &previous, uint64_t &entropy) +{ + bool success{false}; + + auto const it = entropy_cache_.find(previous.body.block_number); + if (entropy_cache_.end() != it) + { + entropy = it->second; + success = true; + } + else + { + // generate the entropy for the previous block + auto const status = + entropy_->GenerateEntropy(previous.body.hash, previous.body.block_number, entropy); + + if (EntropyGeneratorInterface::Status::OK == status) + { + entropy_cache_[previous.body.block_number] = entropy; + success = true; + } + else + { + FETCH_LOG_WARN(LOGGING_NAME, "Unable to lookup entropy for block ", + previous.body.block_number); + } + } + + return success; +} + bool StakeManager::ValidMinerForBlock(Block const &previous, Address const &address) { auto const committee = GetCommittee(previous); diff --git a/libs/ledger/src/genesis_file_creator.cpp b/libs/ledger/src/genesis_file_creator.cpp index a631bde372..3f8b2e8ab0 100644 --- a/libs/ledger/src/genesis_file_creator.cpp +++ b/libs/ledger/src/genesis_file_creator.cpp @@ -16,6 +16,7 @@ // //------------------------------------------------------------------------------ +#include "dkg/dkg_service.hpp" #include "ledger/chain/address.hpp" #include "ledger/chain/block.hpp" #include "ledger/chain/block_coordinator.hpp" @@ -187,6 +188,11 @@ void GenesisFileCreator::LoadFile(std::string const &name) { FETCH_LOG_WARN(LOGGING_NAME, "No stake manager provided when loading from stake file!"); } + + if (dkg_) + { + LoadDKG(doc["beacon"]); + } } } } @@ -342,5 +348,45 @@ void GenesisFileCreator::LoadStake(Variant const &object) } } +void GenesisFileCreator::LoadDKG(variant::Variant const &object) +{ + if (dkg_) + { + std::size_t threshold{1}; + + if (!variant::Extract(object, "threshold", threshold)) + { + return; + } + + if (!object.Has("cabinet")) + { + return; + } + + Variant const &stake_array = object["cabinet"]; + if (!stake_array.IsArray()) + { + return; + } + + dkg::DkgService::CabinetMembers members{}; + for (std::size_t i = 0, end = stake_array.size(); i < end; ++i) + { + auto const &element = stake_array[i]; + + if (!element.IsString()) + { + return; + } + + members.insert(byte_array::FromBase64(element.As())); + } + + // reset the cabinet + dkg_->ResetCabinet(std::move(members), threshold); + } +} + } // namespace ledger } // namespace fetch diff --git a/libs/ledger/tests/unit/naive_entropy_generator_tests.cpp b/libs/ledger/tests/unit/naive_entropy_generator_tests.cpp index bf14c56280..8b34880cb9 100644 --- a/libs/ledger/tests/unit/naive_entropy_generator_tests.cpp +++ b/libs/ledger/tests/unit/naive_entropy_generator_tests.cpp @@ -34,6 +34,7 @@ using fetch::crypto::Hash; using fetch::random::LinearCongruentialGenerator; using fetch::ledger::NaiveEntropyGenerator; using fetch::ledger::Digest; +using fetch::ledger::EntropyGeneratorInterface; using RNG = LinearCongruentialGenerator; using NaiveEntropyGeneratorPtr = std::unique_ptr; @@ -79,8 +80,10 @@ TEST_F(NaiveEntropyGeneratorTests, SimpleCheck) Digest reference_digest = GenerateRandomDigest(); uint64_t const expected_entropy = CalculateEntropy(reference_digest); - uint64_t const actual_entropy = naive_entropy_generator_->GenerateEntropy(reference_digest, 0); + uint64_t actual_entropy = 0; + EXPECT_EQ(EntropyGeneratorInterface::Status::OK, + naive_entropy_generator_->GenerateEntropy(reference_digest, 0, actual_entropy)); EXPECT_EQ(expected_entropy, actual_entropy); } diff --git a/scripts/generate-initial-state.py b/scripts/generate-initial-state.py index 37f14d4290..470502c6af 100755 --- a/scripts/generate-initial-state.py +++ b/scripts/generate-initial-state.py @@ -4,19 +4,34 @@ import struct import hashlib import base64 - +import sys import binascii +import base58 + TOTAL_SUPPLY = 11529975750000000000 +MUDDLE_ADDDRESS_RAW_LENGTH = 64 +DEFAULT_THRESHOLD = 0.6 + + +def _muddle_address(text): + raw = base64.b64decode(text) + if len(raw) != MUDDLE_ADDDRESS_RAW_LENGTH: + print('Incorrect identity length') + sys.exit(1) + return text def parse_commandline(): parser = argparse.ArgumentParser() - parser.add_argument('address', help='The initial staker address') - parser.add_argument('stake', nargs='?', type=int, - default=TOTAL_SUPPLY // 100, help='The initial stake amount') + parser.add_argument('addresses', nargs='+', type=_muddle_address, + help='The initial set of base64 encoded muddle addresses') + parser.add_argument('-s', '--stake-percentage', nargs='?', type=int, + default=1, help='The percentage of tokens to be staked') parser.add_argument( '-o', '--output', default='snapshot.json', help='Path to generated file') + parser.add_argument('-t', '--threshold', type=int, + help='The required threshold') return parser.parse_args() @@ -26,31 +41,82 @@ def calc_resource_id(resource_address): return base64.b64encode(hasher.digest()).decode() -def main(): - args = parse_commandline() +def generate_token_address(muddle_address): + raw_muddle_address = base64.b64decode(muddle_address) - # calculate what the current balance is - stake = args.stake - balance = TOTAL_SUPPLY - stake + hasher1 = hashlib.sha256() + hasher1.update(raw_muddle_address) + token_address_base = hasher1.digest() - # calculate the entry in the state database - resource_id = calc_resource_id('fetch.token.state.' + args.address) + hasher2 = hashlib.sha256() + hasher2.update(token_address_base) + token_address_checksum = hasher2.digest()[:4] + + final_address = token_address_base + token_address_checksum + + return base58.b58encode(final_address).decode() + + +def create_record(address, balance, stake): + resource_id = calc_resource_id('fetch.token.state.' + address) resource_value = base64.b64encode( struct.pack(' len(args): + print('Threshold can\'t me more that the number of stakers') + elif args.threshold < 0: + print('Threshold must be larger than 0') + else: + threshold = args.threshold + + # build up the stake information + individual_balance = TOTAL_SUPPLY // len(args.addresses) + individual_stake = (min(args.stake_percentage, 100) + * individual_balance) // 100 + stakes = [] + state = {} + cabinet = [] + + # build up the configuration for all the stakers + for address in args.addresses: + token_address = generate_token_address(address) + + # update the stake list + stakes.append({ + 'address': token_address, + 'amount': individual_stake, + }) + + # update the initial state + key, value = create_record( + token_address, individual_balance, individual_stake) + state[key] = value + + # update the random beacon config + cabinet.append(address) # form the snapshot data snapshot = { 'version': 1, 'stake': { - 'committeeSize': 1, - 'stakes': [{ - 'address': args.address, - 'amount': stake, - }] + 'committeeSize': 1, # sort of needed at the moment + 'stakes': stakes, + }, + 'beacon': { + 'cabinet': cabinet, + 'threshold': threshold, }, - 'state': { - resource_id: resource_value, - } + 'state': state, } # dump the file diff --git a/vendor/bls b/vendor/bls new file mode 160000 index 0000000000..058f18ee12 --- /dev/null +++ b/vendor/bls @@ -0,0 +1 @@ +Subproject commit 058f18ee12f69b8693f12fd2b20df5a830ead5b7 diff --git a/vendor/mcl b/vendor/mcl new file mode 160000 index 0000000000..17d2b0c18d --- /dev/null +++ b/vendor/mcl @@ -0,0 +1 @@ +Subproject commit 17d2b0c18dc501c7fc7c9ff311572e08a8699ded