diff --git a/include/libp2p/common/sample_peer.hpp b/include/libp2p/common/sample_peer.hpp index c84ead51..fa793ae1 100644 --- a/include/libp2p/common/sample_peer.hpp +++ b/include/libp2p/common/sample_peer.hpp @@ -16,6 +16,46 @@ namespace libp2p { struct SamplePeer { + static constexpr auto Ed25519 = crypto::Key::Type::Ed25519; + static constexpr auto Secp256k1 = crypto::Key::Type::Secp256k1; + + static crypto::KeyPair sampleKeypair(size_t index, + crypto::Key::Type key_type) { + crypto::ed25519::PrivateKey private_key; + std::array seed64; + static_assert(sizeof(seed64) >= sizeof(private_key)); + for (size_t i = 0; i < seed64.size(); ++i) { + seed64.at(i) = i + index; + } + memcpy(private_key.data(), seed64.data(), private_key.size()); + crypto::KeyPair keypair{ + crypto::PublicKey{{key_type, {}}}, + crypto::PrivateKey{{key_type, qtils::ByteVec(private_key)}}, + }; + switch (key_type) { + case crypto::Key::Type::Ed25519: { + crypto::ed25519::Ed25519ProviderImpl ed25519; + keypair.publicKey.data = + qtils::ByteVec(ed25519.derive(private_key).value()); + break; + } + case crypto::Key::Type::Secp256k1: { + crypto::secp256k1::Secp256k1ProviderImpl secp256k1{nullptr}; + keypair.publicKey.data = + qtils::ByteVec(secp256k1.derive(private_key).value()); + break; + } + default: { + abort(); + } + } + return keypair; + } + + static uint16_t samplePort(size_t index) { + return 10000 + index; + } + SamplePeer(size_t index, uint16_t port, crypto::KeyPair keypair, @@ -31,48 +71,26 @@ namespace libp2p { connect{connect}, connect_info{connect_info} {} - SamplePeer(size_t index, crypto::Key::Type key_type) + SamplePeer(size_t index, + std::string_view ip, + uint16_t port, + crypto::KeyPair keypair) : SamplePeer{[&] { - uint16_t port = 10000 + index; - crypto::ed25519::PrivateKey private_key; - for (size_t i = 0; i < private_key.size(); ++i) { - private_key.at(i) = i + index; - } - crypto::KeyPair keypair{ - crypto::PublicKey{{key_type, {}}}, - crypto::PrivateKey{{key_type, qtils::ByteVec(private_key)}}, - }; - switch (key_type) { - case crypto::Key::Type::Ed25519: { - crypto::ed25519::Ed25519ProviderImpl ed25519; - keypair.publicKey.data = - qtils::ByteVec(ed25519.derive(private_key).value()); - break; - } - case crypto::Key::Type::Secp256k1: { - crypto::secp256k1::Secp256k1ProviderImpl secp256k1{nullptr}; - keypair.publicKey.data = - qtils::ByteVec(secp256k1.derive(private_key).value()); - break; - } - default: { - abort(); - } - } auto peer_id = peer::IdentityManager{ keypair, std::make_shared( nullptr)} .getId(); - auto listen = - Multiaddress::create( - std::format("/ip4/127.0.0.1/udp/{}/quic-v1", port)) - .value(); - auto connect = - Multiaddress::create( - std::format("{}/p2p/{}", listen, peer_id.toBase58())) - .value(); + auto listen = Multiaddress::create( + std::format("/ip4/0.0.0.0/udp/{}/quic-v1", port)) + .value(); + auto connect = Multiaddress::create( + std::format("/ip4/{}/udp/{}/quic-v1/p2p/{}", + ip, + port, + peer_id.toBase58())) + .value(); return SamplePeer{ index, port, @@ -84,12 +102,26 @@ namespace libp2p { }; }()} {} + SamplePeer(size_t index, + std::string_view ip, + uint16_t port, + crypto::Key::Type key_type) + : SamplePeer{index, ip, port, sampleKeypair(index, key_type)} {} + + SamplePeer(size_t index, crypto::Key::Type key_type) + : SamplePeer{ + index, + "127.0.0.1", + samplePort(index), + key_type, + } {} + static SamplePeer makeEd25519(size_t index) { - return SamplePeer{index, crypto::Key::Type::Ed25519}; + return SamplePeer{index, Ed25519}; } static SamplePeer makeSecp256k1(size_t index) { - return SamplePeer{index, crypto::Key::Type::Secp256k1}; + return SamplePeer{index, Secp256k1}; } size_t index; diff --git a/include/libp2p/crypto/secp256k1_provider.hpp b/include/libp2p/crypto/secp256k1_provider.hpp index a8c364e6..70dc42bf 100644 --- a/include/libp2p/crypto/secp256k1_provider.hpp +++ b/include/libp2p/crypto/secp256k1_provider.hpp @@ -38,6 +38,8 @@ namespace libp2p::crypto::secp256k1 { */ virtual outcome::result sign(BytesIn message, const PrivateKey &key) const = 0; + virtual outcome::result signCompact( + const Prehashed &prehashed, const PrivateKey &key) const = 0; /** * @brief Verify signature for a message @@ -49,6 +51,10 @@ namespace libp2p::crypto::secp256k1 { virtual outcome::result verify(BytesIn message, const Signature &signature, const PublicKey &key) const = 0; + virtual outcome::result verifyCompact( + const Prehashed &prehashed, + const SignatureCompact &signature, + const PublicKey &key) const = 0; virtual ~Secp256k1Provider() = default; }; diff --git a/include/libp2p/crypto/secp256k1_provider/secp256k1_provider_impl.hpp b/include/libp2p/crypto/secp256k1_provider/secp256k1_provider_impl.hpp index f5aff54d..6096c301 100644 --- a/include/libp2p/crypto/secp256k1_provider/secp256k1_provider_impl.hpp +++ b/include/libp2p/crypto/secp256k1_provider/secp256k1_provider_impl.hpp @@ -26,12 +26,23 @@ namespace libp2p::crypto::secp256k1 { outcome::result sign(BytesIn message, const PrivateKey &key) const override; + outcome::result signCompact( + const Prehashed &prehashed, const PrivateKey &key) const override; outcome::result verify(BytesIn message, const Signature &signature, const PublicKey &key) const override; + outcome::result verifyCompact(const Prehashed &prehashed, + const SignatureCompact &signature, + const PublicKey &key) const override; private: + outcome::result signRaw( + const Prehashed &prehashed, const PrivateKey &key) const; + outcome::result verifyRaw(const Prehashed &prehashed, + const secp256k1_ecdsa_signature &ffi_sig, + const PublicKey &key) const; + std::shared_ptr random_; std::unique_ptr ctx_; }; diff --git a/include/libp2p/crypto/secp256k1_types.hpp b/include/libp2p/crypto/secp256k1_types.hpp index 628cbe9c..e264c522 100644 --- a/include/libp2p/crypto/secp256k1_types.hpp +++ b/include/libp2p/crypto/secp256k1_types.hpp @@ -13,12 +13,16 @@ namespace libp2p::crypto::secp256k1 { static const size_t kPrivateKeyLength = 32; static const size_t kPublicKeyLength = 33; + static const size_t kSignatureCompactLength = 64; + static const size_t kPrehashedLength = 32; /** * @brief Common types */ using PrivateKey = std::array; using PublicKey = std::array; using Signature = std::vector; + using SignatureCompact = std::array; + using Prehashed = std::array; /** * @struct Key pair diff --git a/include/libp2p/crypto/sha/keccak.hpp b/include/libp2p/crypto/sha/keccak.hpp new file mode 100644 index 00000000..6fa987e1 --- /dev/null +++ b/include/libp2p/crypto/sha/keccak.hpp @@ -0,0 +1,26 @@ +// https://github.com/nayuki/Bitcoin-Cryptography-Library/blob/master/cpp/Keccak256.hpp +// Not using https://vcpkg.io/en/package/keccak-tiny from vcpkg because it only +// exports sha3, not keccak + +#pragma once + +#include + +namespace libp2p { + class Keccak { + public: + using Hash32 = qtils::ByteArr<32>; + + void update(uint8_t byte); + Keccak &update(qtils::BytesIn input); + Hash32 finalize(); + Hash32 hash() const; + static Hash32 hash(qtils::BytesIn input); + + private: + void absorb(); + + uint64_t state[5][5] = {}; + size_t blockOff = 0; + }; +} // namespace libp2p diff --git a/src/crypto/secp256k1_provider/secp256k1_provider_impl.cpp b/src/crypto/secp256k1_provider/secp256k1_provider_impl.cpp index 0b4aad40..803a410e 100644 --- a/src/crypto/secp256k1_provider/secp256k1_provider_impl.cpp +++ b/src/crypto/secp256k1_provider/secp256k1_provider_impl.cpp @@ -49,17 +49,7 @@ namespace libp2p::crypto::secp256k1 { outcome::result Secp256k1ProviderImpl::sign( BytesIn message, const PrivateKey &key) const { - OUTCOME_TRY(digest, sha256(message)); - secp256k1_ecdsa_signature ffi_sig; - if (secp256k1_ecdsa_sign(ctx_.get(), - &ffi_sig, - digest.data(), - key.data(), - secp256k1_nonce_function_rfc6979, - nullptr) - == 0) { - return CryptoProviderError::SIGNATURE_GENERATION_FAILED; - } + OUTCOME_TRY(ffi_sig, signRaw(sha256(message).value(), key)); uint8_t empty = 0; size_t size = 0; secp256k1_ecdsa_signature_serialize_der( @@ -74,21 +64,65 @@ namespace libp2p::crypto::secp256k1 { return signature; } + outcome::result Secp256k1ProviderImpl::signCompact( + const Prehashed &prehashed, const PrivateKey &key) const { + OUTCOME_TRY(ffi_sig, signRaw(prehashed, key)); + SignatureCompact signature; + secp256k1_ecdsa_signature_serialize_compact( + ctx_.get(), signature.data(), &ffi_sig); + return signature; + } + outcome::result Secp256k1ProviderImpl::verify( BytesIn message, const Signature &signature, const PublicKey &key) const { - OUTCOME_TRY(digest, sha256(message)); - secp256k1_pubkey ffi_pub; - if (secp256k1_ec_pubkey_parse(ctx_.get(), &ffi_pub, key.data(), key.size()) + secp256k1_ecdsa_signature ffi_sig; + if (secp256k1_ecdsa_signature_parse_der( + ctx_.get(), &ffi_sig, signature.data(), signature.size()) == 0) { return CryptoProviderError::SIGNATURE_VERIFICATION_FAILED; } + return verifyRaw(sha256(message).value(), ffi_sig, key); + } + + outcome::result Secp256k1ProviderImpl::verifyCompact( + const Prehashed &prehashed, + const SignatureCompact &signature, + const PublicKey &key) const { secp256k1_ecdsa_signature ffi_sig; - if (secp256k1_ecdsa_signature_parse_der( - ctx_.get(), &ffi_sig, signature.data(), signature.size()) + if (secp256k1_ecdsa_signature_parse_compact( + ctx_.get(), &ffi_sig, signature.data()) + == 0) { + return CryptoProviderError::SIGNATURE_VERIFICATION_FAILED; + } + return verifyRaw(prehashed, ffi_sig, key); + } + + outcome::result Secp256k1ProviderImpl::signRaw( + const Prehashed &prehashed, const PrivateKey &key) const { + secp256k1_ecdsa_signature ffi_sig; + if (secp256k1_ecdsa_sign(ctx_.get(), + &ffi_sig, + prehashed.data(), + key.data(), + secp256k1_nonce_function_rfc6979, + nullptr) + == 0) { + return CryptoProviderError::SIGNATURE_GENERATION_FAILED; + } + return ffi_sig; + } + + outcome::result Secp256k1ProviderImpl::verifyRaw( + const Prehashed &prehashed, + const secp256k1_ecdsa_signature &ffi_sig, + const PublicKey &key) const { + secp256k1_pubkey ffi_pub; + if (secp256k1_ec_pubkey_parse(ctx_.get(), &ffi_pub, key.data(), key.size()) == 0) { return CryptoProviderError::SIGNATURE_VERIFICATION_FAILED; } - return secp256k1_ecdsa_verify(ctx_.get(), &ffi_sig, digest.data(), &ffi_pub) + return secp256k1_ecdsa_verify( + ctx_.get(), &ffi_sig, prehashed.data(), &ffi_pub) == 1; } } // namespace libp2p::crypto::secp256k1 diff --git a/src/crypto/sha/CMakeLists.txt b/src/crypto/sha/CMakeLists.txt index 2114068f..32a45e8a 100644 --- a/src/crypto/sha/CMakeLists.txt +++ b/src/crypto/sha/CMakeLists.txt @@ -5,6 +5,7 @@ # libp2p_add_library(p2p_sha + keccak.cpp sha1.cpp sha256.cpp sha512.cpp diff --git a/src/crypto/sha/keccak.cpp b/src/crypto/sha/keccak.cpp new file mode 100644 index 00000000..298d82d9 --- /dev/null +++ b/src/crypto/sha/keccak.cpp @@ -0,0 +1,109 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +namespace libp2p { + constexpr size_t HASH_LEN = 32; + constexpr size_t BLOCK_SIZE = 200 - HASH_LEN * 2; + + void Keccak::update(uint8_t byte) { + int j = blockOff >> 3; + state[j % 5][j / 5] ^= static_cast(byte) << ((blockOff & 7) << 3); + blockOff++; + if (blockOff == BLOCK_SIZE) { + absorb(); + blockOff = 0; + } + } + + Keccak &Keccak::update(qtils::BytesIn input) { + for (auto &x : input) { + update(x); + } + return *this; + } + + Keccak::Hash32 Keccak::finalize() { + Hash32 hash; + // Final block and padding + { + int i = blockOff >> 3; + state[i % 5][i / 5] ^= UINT64_C(0x01) << ((blockOff & 7) << 3); + blockOff = BLOCK_SIZE - 1; + int j = blockOff >> 3; + state[j % 5][j / 5] ^= UINT64_C(0x80) << ((blockOff & 7) << 3); + absorb(); + } + // Uint64 array to bytes in little endian + for (int i = 0; i < HASH_LEN; i++) { + int j = i >> 3; + hash[i] = static_cast(state[j % 5][j / 5] >> ((i & 7) << 3)); + } + return hash; + } + + Keccak::Hash32 Keccak::hash() const { + auto copy = *this; + return copy.finalize(); + } + + Keccak::Hash32 Keccak::hash(qtils::BytesIn input) { + return Keccak{}.update(input).hash(); + } + + void Keccak::absorb() { + auto rotl64 = [](uint64_t x, uint8_t i) { + return (x << i) | (x >> (64 - i)); + }; + constexpr size_t NUM_ROUNDS = 24; + constexpr uint8_t ROTATION[5][5] = { + {0, 36, 3, 41, 18}, + {1, 44, 10, 45, 2}, + {62, 6, 43, 15, 61}, + {28, 55, 25, 21, 56}, + {27, 20, 39, 8, 14}, + }; + uint64_t (*a)[5] = state; + uint8_t r = 1; // LFSR + for (int i = 0; i < NUM_ROUNDS; i++) { + // Theta step + uint64_t c[5] = {}; + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + c[x] ^= a[x][y]; + } + } + for (int x = 0; x < 5; x++) { + uint64_t d = c[(x + 4) % 5] ^ rotl64(c[(x + 1) % 5], 1); + for (int y = 0; y < 5; y++) { + a[x][y] ^= d; + } + } + + // Rho and pi steps + uint64_t b[5][5]; + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + b[y][(x * 2 + y * 3) % 5] = rotl64(a[x][y], ROTATION[x][y]); + } + } + + // Chi step + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + a[x][y] = b[x][y] ^ (~b[(x + 1) % 5][y] & b[(x + 2) % 5][y]); + } + } + + // Iota step + for (int j = 0; j < 7; j++) { + a[0][0] ^= static_cast(r & 1) << ((1 << j) - 1); + r = static_cast((r << 1) ^ ((r >> 7) * 0x171)); + } + } + } +} // namespace libp2p