Skip to content

account/GeniusSigner.cpp

Namespaces

Name
sgns

Source code

#include "account/GeniusSigner.hpp"

#include <openssl/rand.h>
#include <secp256k1.h>

#include <algorithm>
#include <array>
#include <cstdlib>
#include <memory>

#include "base/hexutil.hpp"
#include "base/logger.hpp"
#include "crypto/sha/sha256.hpp"

namespace
{
    sgns::base::Logger genius_signer_logger()
    {
        return sgns::base::createLogger( "GeniusSigner" );
    }

    const secp256k1_context *GetSecp256k1Context()
    {
        using Context = std::unique_ptr<secp256k1_context, decltype( &secp256k1_context_destroy )>;
        static const Context context( secp256k1_context_create( SECP256K1_CONTEXT_NONE ), &secp256k1_context_destroy );
        if ( context == nullptr )
        {
            genius_signer_logger()->critical( "Could not create the secp256k1 context" );
            std::abort();
        }
        return context.get();
    }

    std::array<uint8_t, 32> GeniusMessageHash( const std::vector<uint8_t> &data )
    {
        const auto first  = sgns::crypto::sha256( gsl::make_span( data.data(), data.size() ) );
        const auto second = sgns::crypto::sha256( gsl::make_span( first.data(), first.size() ) );

        std::array<uint8_t, 32> hash{};
        std::copy( second.begin(), second.end(), hash.begin() );
        return hash;
    }
} // namespace

namespace sgns
{
    GeniusSigner GeniusSigner::Generate()
    {
        const auto *context = GetSecp256k1Context();

        PrivateKey private_key{};
        // A uniformly random 32-byte string lands outside the curve order with
        // negligible probability, but retry so the result is always usable.
        do
        {
            if ( RAND_bytes( private_key.data(), static_cast<int>( private_key.size() ) ) != 1 )
            {
                genius_signer_logger()->critical( "Could not obtain entropy for a new Genius key" );
                std::abort();
            }
        } while ( secp256k1_ec_seckey_verify( context, private_key.data() ) == 0 );

        return GeniusSigner( private_key );
    }

    GeniusSigner::GeniusSigner( const PrivateKey &private_key ) : private_key_( private_key )
    {
        const auto *context = GetSecp256k1Context();

        secp256k1_pubkey public_key;
        if ( secp256k1_ec_pubkey_create( context, &public_key, private_key_.data() ) == 0 )
        {
            genius_signer_logger()->error( "Could not derive a public key from the supplied secret key" );
            return;
        }

        std::array<uint8_t, 65> uncompressed_public_key{};
        size_t                  length = uncompressed_public_key.size();
        secp256k1_ec_pubkey_serialize( context,
                                       uncompressed_public_key.data(),
                                       &length,
                                       &public_key,
                                       SECP256K1_EC_UNCOMPRESSED );

        // Drop the 0x04 uncompressed tag: the Genius address is just X || Y.
        address_ = base::hex_lower( gsl::make_span( uncompressed_public_key.data() + 1, 64 ) );
    }

    std::string GeniusSigner::GetAddress() const
    {
        return address_;
    }

    bool GeniusSigner::VerifySignature( const std::string          &address,
                                        std::string_view            signature,
                                        const std::vector<uint8_t> &data )
    {
        if ( signature.size() != SIGNATURE_SIZE )
        {
            genius_signer_logger()->error( "Incorrect signature size {}, expected {}",
                                           signature.size(),
                                           SIGNATURE_SIZE );
            return false;
        }

        auto public_key_bytes = base::unhex( address );
        if ( public_key_bytes.has_error() || public_key_bytes.value().size() != 64 )
        {
            return false;
        }

        const auto *context = GetSecp256k1Context();

        std::array<uint8_t, 65> uncompressed_public_key{};
        uncompressed_public_key.front() = SECP256K1_TAG_PUBKEY_UNCOMPRESSED;
        std::copy( public_key_bytes.value().begin(),
                   public_key_bytes.value().end(),
                   uncompressed_public_key.begin() + 1 );

        secp256k1_pubkey public_key;
        if ( secp256k1_ec_pubkey_parse( context,
                                        &public_key,
                                        uncompressed_public_key.data(),
                                        uncompressed_public_key.size() ) == 0 )
        {
            return false;
        }

        // Genius signatures store each scalar least-significant byte first; libsecp256k1 uses big endian.
        std::array<uint8_t, SIGNATURE_SIZE> compact_signature{};
        std::reverse_copy( signature.begin(), signature.begin() + 32, compact_signature.begin() );
        std::reverse_copy( signature.begin() + 32, signature.end(), compact_signature.begin() + 32 );

        secp256k1_ecdsa_signature parsed_signature;
        if ( secp256k1_ecdsa_signature_parse_compact( context, &parsed_signature, compact_signature.data() ) == 0 )
        {
            return false;
        }
        secp256k1_ecdsa_signature_normalize( context, &parsed_signature, &parsed_signature );

        const std::array<uint8_t, 32> message_hash = GeniusMessageHash( data );

        return secp256k1_ecdsa_verify( context, &parsed_signature, message_hash.data(), &public_key ) == 1;
    }

    bool GeniusSigner::VerifySignature( const std::string          &address,
                                        const std::vector<uint8_t> &signature,
                                        const std::vector<uint8_t> &data )
    {
        const std::string_view signature_view = signature.empty()
                                                    ? std::string_view{}
                                                    : std::string_view(
                                                          reinterpret_cast<const char *>( signature.data() ),
                                                          signature.size() );
        return VerifySignature( address, signature_view, data );
    }

    std::vector<uint8_t> GeniusSigner::Sign( const std::vector<uint8_t> &data ) const
    {
        const auto *context = GetSecp256k1Context();

        const std::array<uint8_t, 32> message_hash = GeniusMessageHash( data );

        secp256k1_ecdsa_signature signature;
        if ( secp256k1_ecdsa_sign( context, &signature, message_hash.data(), private_key_.data(), nullptr, nullptr ) ==
             0 )
        {
            genius_signer_logger()->error( "Could not sign data with the account key" );
            return {};
        }

        std::array<uint8_t, SIGNATURE_SIZE> compact_signature{};
        secp256k1_ecdsa_signature_serialize_compact( context, compact_signature.data(), &signature );

        std::vector<uint8_t> signed_vector( SIGNATURE_SIZE );
        std::reverse_copy( compact_signature.begin(), compact_signature.begin() + 32, signed_vector.begin() );
        std::reverse_copy( compact_signature.begin() + 32, compact_signature.end(), signed_vector.begin() + 32 );
        return signed_vector;
    }
} // namespace sgns

Updated on 2026-08-28 at 01:08:43 +0000