base/gossip_auth.hpp¶
Application-layer payload authentication for private-network gossip (CR-G01). More...
Namespaces¶
| Name |
|---|
| sgns |
| sgns::base |
| sgns::base::detail |
Classes¶
| Name | |
|---|---|
| struct | sgns::base::OpenedGossipPayload |
Types¶
| Name | |
|---|---|
| enum class | GossipPayloadAuthError { NOT_AN_ENVELOPE = 1, MALFORMED_ENVELOPE, KEY_FROM_MISMATCH, SIGNATURE_INVALID, SEAL_FAILED, DERIVE_FAILED} Failure kinds reported by OpenGossipPayload / SealGossipPayload. |
| template <typename T > using libp2p::outcome::result< T, GossipPayloadAuthError, libp2p::outcome::policy::terminate > |
GossipAuthResult |
Functions¶
| Name | |
|---|---|
| gsl::span< const uint8_t > | StringSpan(const std::string & s) |
| std::shared_ptr< libp2p::crypto::CryptoProviderImpl > | MakeGossipAuthProvider() |
| libp2p::crypto::CryptoProvider & | GossipAuthCryptoProvider() |
| libp2p::crypto::marshaller::KeyMarshaller & | GossipAuthKeyMarshaller() |
| void | AppendU32Be(std::vector< uint8_t > & out, uint32_t value) |
| bool | ReadU32Be(gsl::span< const uint8_t > data, size_t & offset, uint32_t & value) |
| std::vector< uint8_t > | BuildSignableBytes(gsl::span< const uint8_t > from_bytes, gsl::span< const uint8_t > payload) Canonical signable bytes: magic + u32be(from length) + from + payload. |
| GossipAuthResult< libp2p::common::ByteArray > | DeriveGossipFromBytes(const libp2p::crypto::KeyPair & keypair) Derives the transport from-bytes a publisher sealing with keypair must present: PeerId::fromPublicKey(marshalled public key).toVector(). Call once and reuse – this equals the local_peer_id_ the vendored gossip stamps into from at publish. |
| GossipAuthResult< libp2p::common::ByteArray > | SealGossipPayload(const libp2p::crypto::KeyPair & keypair, gsl::span< const uint8_t > from_bytes, gsl::span< const uint8_t > payload) Seals payload into an authenticated envelope signed with keypair's private key (CR-G01 publisher side). |
| GossipAuthResult< OpenedGossipPayload > | OpenGossipPayload(gsl::span< const uint8_t > from_bytes, gsl::span< const uint8_t > wire_data) Verifies an authenticated gossip envelope (CR-G01 gate side). |
Attributes¶
| Name | |
|---|---|
| constexpr std::array< uint8_t, 12 > | kGossipAuthEnvelopeMagic 12-byte ASCII magic prefix identifying an authenticated gossip envelope. |
Detailed Description¶
Application-layer payload authentication for private-network gossip (CR-G01).
The vendored gossip wire Message carries signature/key fields, but the subscriber-facing type Gossip::Message exposes ONLY {from, topic, data} (thirdparty/libp2p .../protocol/gossip/gossip.hpp:129-135) and the vendored receive/forward path performs zero signature verification – so those gossip-layer fields are NOT reachable by any SGNUS subscriber and are NOT consumed by any SGNUS gate. The owner-sanctioned equivalent authenticated mapping is this application-layer envelope:
- Publishers seal gossip payloads with the SAME libp2p keypair that constructs their gossip host, so PeerId::fromPublicKey(embedded key) equals the from-field the vendored gossip stamps (GossipCore::publish sets from = local_peer_id_ unconditionally) and equals the identity carried in NetworkRegistry membership records.
- Gates verify the envelope BEFORE consulting membership: unmarshal the embedded public key, check PeerId::fromPublicKey(key) equals PeerId::fromBytes(from), and verify the signature over the canonical signable bytes. ANY failure = deny (fail-closed, mirroring the empty-from denial of 15-11/15-13).
Envelope wire format (all integers big-endian):
offset size field 0 12 ASCII magic prefix (kGossipAuthEnvelopeMagic) 12 4 marshaled-public-key length N 16 N marshaled protobuf public key (Ed25519 etc.) 16+N 4 signature length S 20+N S signature over the canonical signable bytes 20+N+S * payload remainder
Canonical signable bytes = magic prefix + u32be(from length) + from bytes + payload. The signature covers the payload AND the transport from-field, so a sealed payload replayed under a different from (or any payload byte flip) fails verification.
Sealing changes the wire format ONLY inside private networks: callers seal exactly when a membership filter is installed; public nodes publish raw and pass through raw (byte-identical). The envelope intentionally carries no topic/freshness binding (residual accepted, T-15-14-04): gossip message-id dedup bounds naive replay and downstream protobuf/state machines carry their own ids.
Header-only: no networkregistry/ and no crdt/ dependencies – the gates in crdt and processing include only this header.
Types Documentation¶
enum GossipPayloadAuthError¶
| Enumerator | Value | Description |
|---|---|---|
| NOT_AN_ENVELOPE | 1 | wire data carries no envelope magic (raw public payload) |
| MALFORMED_ENVELOPE | truncated header/lengths or unparseable embedded public key | |
| KEY_FROM_MISMATCH | PeerId::fromPublicKey(embedded key) != PeerId::fromBytes(from) | |
| SIGNATURE_INVALID | signature does not verify over the canonical signable bytes | |
| SEAL_FAILED | sealing-side failure (marshal/sign) | |
| DERIVE_FAILED | from-bytes derivation failure (marshal/PeerId) |
Failure kinds reported by OpenGossipPayload / SealGossipPayload.
using GossipAuthResult¶
template <typename T >
using sgns::base::GossipAuthResult = typedef libp2p::outcome::result<T, GossipPayloadAuthError, libp2p::outcome::policy::terminate>;
Result type carrying the local error enum. The terminate policy avoids instantiating outcome's exception-throw path (which only supports std::error_code error types); every caller MUST check has_error() before value() – all gate call sites do.
Functions Documentation¶
function StringSpan¶
std::string -> byte-span convenience for string-serialized protobuf payloads handed to SealGossipPayload.
function MakeGossipAuthProvider¶
Crypto provider instantiation recipe copied from crdt/globaldb/keypair_file_storage.cpp:20-52.
function GossipAuthCryptoProvider¶
Function-local singletons (thread-safe magic statics); sign/verify/marshal are const operations on the underlying providers.
function GossipAuthKeyMarshaller¶
function AppendU32Be¶
function ReadU32Be¶
Reads a u32 big-endian at offset, advancing it by 4. Fails when fewer than 4 bytes remain.
function BuildSignableBytes¶
inline std::vector< uint8_t > BuildSignableBytes(
gsl::span< const uint8_t > from_bytes,
gsl::span< const uint8_t > payload
)
Canonical signable bytes: magic + u32be(from length) + from + payload.
function DeriveGossipFromBytes¶
inline GossipAuthResult< libp2p::common::ByteArray > DeriveGossipFromBytes(
const libp2p::crypto::KeyPair & keypair
)
Derives the transport from-bytes a publisher sealing with keypair must present: PeerId::fromPublicKey(marshalled public key).toVector(). Call once and reuse – this equals the local_peer_id_ the vendored gossip stamps into from at publish.
function SealGossipPayload¶
inline GossipAuthResult< libp2p::common::ByteArray > SealGossipPayload(
const libp2p::crypto::KeyPair & keypair,
gsl::span< const uint8_t > from_bytes,
gsl::span< const uint8_t > payload
)
Seals payload into an authenticated envelope signed with keypair's private key (CR-G01 publisher side).
Parameters:
- keypair Gossip-host keypair (the same one that constructed the GossipPubSub host).
- from_bytes Publisher's own from-bytes – MUST equal DeriveGossipFromBytes(keypair) or receivers will reject the binding.
- payload Serialized application payload to seal.
Return: Envelope bytes to publish, or SEAL_FAILED on marshal/sign error.
function OpenGossipPayload¶
inline GossipAuthResult< OpenedGossipPayload > OpenGossipPayload(
gsl::span< const uint8_t > from_bytes,
gsl::span< const uint8_t > wire_data
)
Verifies an authenticated gossip envelope (CR-G01 gate side).
Parameters:
- from_bytes Transport gossip from-field (wire-supplied).
- wire_data Message payload as received (envelope or raw).
Return: OpenedGossipPayload with the authenticated PeerId (equal to the from PeerId) and the inner payload view.
Check order (every failure denies under a set membership filter):
- magic present – else NOT_AN_ENVELOPE (raw public payload; a set filter treats this as deny, fail-closed);
- lengths parse and the embedded public key unmarshals – else MALFORMED_ENVELOPE;
- PeerId::fromPublicKey(embedded key) == PeerId::fromBytes(from) (an empty/malformed from also fails here) – else KEY_FROM_MISMATCH: a same-PSK peer forging from=
cannot pass, because the embedded key does not derive that PeerId; - signature verifies over the recomputed signable bytes – else SIGNATURE_INVALID: covers payload AND from, so tampering either fails.
Attributes Documentation¶
variable kGossipAuthEnvelopeMagic¶
constexpr std::array< uint8_t, 12 > kGossipAuthEnvelopeMagic = {
'S', 'G', 'N', 'S', 'G', 'O', 'S', 'S', 'I', 'P', '0', '1' };
12-byte ASCII magic prefix identifying an authenticated gossip envelope.
Source code¶
#ifndef SUPERGENIUS_BASE_GOSSIP_AUTH_HPP
#define SUPERGENIUS_BASE_GOSSIP_AUTH_HPP
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <gsl/span>
#include <libp2p/common/byteutil.hpp>
#include <libp2p/crypto/crypto_provider.hpp>
#include <libp2p/crypto/crypto_provider/crypto_provider_impl.hpp>
#include <libp2p/crypto/ecdsa_provider/ecdsa_provider_impl.hpp>
#include <libp2p/crypto/ed25519_provider/ed25519_provider_impl.hpp>
#include <libp2p/crypto/hmac_provider/hmac_provider_impl.hpp>
#include <libp2p/crypto/key.hpp>
#include <libp2p/crypto/key_marshaller.hpp>
#include <libp2p/crypto/key_marshaller/key_marshaller_impl.hpp>
#include <libp2p/crypto/key_validator.hpp>
#include <libp2p/crypto/key_validator/key_validator_impl.hpp>
#include <libp2p/crypto/protobuf/protobuf_key.hpp>
#include <libp2p/crypto/random_generator/boost_generator.hpp>
#include <libp2p/crypto/rsa_provider/rsa_provider_impl.hpp>
#include <libp2p/crypto/secp256k1_provider/secp256k1_provider_impl.hpp>
#include <libp2p/outcome/outcome.hpp>
#include <libp2p/peer/peer_id.hpp>
namespace sgns::base
{
inline constexpr std::array<uint8_t, 12> kGossipAuthEnvelopeMagic = {
'S', 'G', 'N', 'S', 'G', 'O', 'S', 'S', 'I', 'P', '0', '1' };
enum class GossipPayloadAuthError
{
NOT_AN_ENVELOPE = 1,
MALFORMED_ENVELOPE,
KEY_FROM_MISMATCH,
SIGNATURE_INVALID,
SEAL_FAILED,
DERIVE_FAILED,
};
template <typename T>
using GossipAuthResult =
libp2p::outcome::result<T, GossipPayloadAuthError, libp2p::outcome::policy::terminate>;
struct OpenedGossipPayload
{
libp2p::peer::PeerId authenticated_peer;
gsl::span<const uint8_t> payload;
};
namespace detail
{
inline gsl::span<const uint8_t> StringSpan( const std::string &s )
{
return gsl::span<const uint8_t>( reinterpret_cast<const uint8_t *>( s.data() ), s.size() );
}
inline std::shared_ptr<libp2p::crypto::CryptoProviderImpl> MakeGossipAuthProvider()
{
return std::make_shared<libp2p::crypto::CryptoProviderImpl>(
std::make_shared<libp2p::crypto::random::BoostRandomGenerator>(),
std::make_shared<libp2p::crypto::ed25519::Ed25519ProviderImpl>(),
std::make_shared<libp2p::crypto::rsa::RsaProviderImpl>(),
std::make_shared<libp2p::crypto::ecdsa::EcdsaProviderImpl>(),
std::make_shared<libp2p::crypto::secp256k1::Secp256k1ProviderImpl>(),
std::make_shared<libp2p::crypto::hmac::HmacProviderImpl>() );
}
inline libp2p::crypto::CryptoProvider &GossipAuthCryptoProvider()
{
static const auto kProvider = MakeGossipAuthProvider();
return *kProvider;
}
inline libp2p::crypto::marshaller::KeyMarshaller &GossipAuthKeyMarshaller()
{
static const auto kValidator =
std::make_shared<libp2p::crypto::validator::KeyValidatorImpl>( MakeGossipAuthProvider() );
static libp2p::crypto::marshaller::KeyMarshallerImpl kMarshaller{ kValidator };
return kMarshaller;
}
inline void AppendU32Be( std::vector<uint8_t> &out, uint32_t value )
{
out.push_back( static_cast<uint8_t>( value >> 24 ) );
out.push_back( static_cast<uint8_t>( value >> 16 ) );
out.push_back( static_cast<uint8_t>( value >> 8 ) );
out.push_back( static_cast<uint8_t>( value ) );
}
inline bool ReadU32Be( gsl::span<const uint8_t> data, size_t &offset, uint32_t &value )
{
const size_t size = static_cast<size_t>( data.size() );
if ( size < offset || size - offset < 4 )
{
return false;
}
value = ( static_cast<uint32_t>( data[offset] ) << 24 )
| ( static_cast<uint32_t>( data[offset + 1] ) << 16 )
| ( static_cast<uint32_t>( data[offset + 2] ) << 8 )
| static_cast<uint32_t>( data[offset + 3] );
offset += 4;
return true;
}
inline std::vector<uint8_t> BuildSignableBytes( gsl::span<const uint8_t> from_bytes,
gsl::span<const uint8_t> payload )
{
std::vector<uint8_t> signable;
signable.reserve( kGossipAuthEnvelopeMagic.size() + 4 + from_bytes.size() + payload.size() );
signable.insert( signable.end(), kGossipAuthEnvelopeMagic.begin(), kGossipAuthEnvelopeMagic.end() );
AppendU32Be( signable, static_cast<uint32_t>( from_bytes.size() ) );
signable.insert( signable.end(), from_bytes.begin(), from_bytes.end() );
signable.insert( signable.end(), payload.begin(), payload.end() );
return signable;
}
} // namespace detail
inline GossipAuthResult<libp2p::common::ByteArray> DeriveGossipFromBytes(
const libp2p::crypto::KeyPair &keypair )
{
auto marshalled = detail::GossipAuthKeyMarshaller().marshal( keypair.publicKey );
if ( marshalled.has_error() )
{
return GossipPayloadAuthError::DERIVE_FAILED;
}
auto peer_id = libp2p::peer::PeerId::fromPublicKey( marshalled.value() );
if ( peer_id.has_error() )
{
return GossipPayloadAuthError::DERIVE_FAILED;
}
return libp2p::outcome::success( peer_id.value().toVector() );
}
inline GossipAuthResult<libp2p::common::ByteArray> SealGossipPayload(
const libp2p::crypto::KeyPair &keypair,
gsl::span<const uint8_t> from_bytes,
gsl::span<const uint8_t> payload )
{
auto marshalled_key = detail::GossipAuthKeyMarshaller().marshal( keypair.publicKey );
if ( marshalled_key.has_error() )
{
return GossipPayloadAuthError::SEAL_FAILED;
}
const auto signable = detail::BuildSignableBytes( from_bytes, payload );
auto signature = detail::GossipAuthCryptoProvider().sign( signable, keypair.privateKey );
if ( signature.has_error() )
{
return GossipPayloadAuthError::SEAL_FAILED;
}
const auto &key_bytes = marshalled_key.value().key;
std::vector<uint8_t> envelope;
envelope.reserve( kGossipAuthEnvelopeMagic.size() + 4 + key_bytes.size() + 4
+ signature.value().size() + payload.size() );
envelope.insert( envelope.end(), kGossipAuthEnvelopeMagic.begin(), kGossipAuthEnvelopeMagic.end() );
detail::AppendU32Be( envelope, static_cast<uint32_t>( key_bytes.size() ) );
envelope.insert( envelope.end(), key_bytes.begin(), key_bytes.end() );
detail::AppendU32Be( envelope, static_cast<uint32_t>( signature.value().size() ) );
envelope.insert( envelope.end(), signature.value().begin(), signature.value().end() );
envelope.insert( envelope.end(), payload.begin(), payload.end() );
return libp2p::outcome::success( std::move( envelope ) );
}
inline GossipAuthResult<OpenedGossipPayload> OpenGossipPayload(
gsl::span<const uint8_t> from_bytes,
gsl::span<const uint8_t> wire_data )
{
if ( static_cast<size_t>( wire_data.size() ) < kGossipAuthEnvelopeMagic.size()
|| !std::equal( kGossipAuthEnvelopeMagic.begin(),
kGossipAuthEnvelopeMagic.end(),
wire_data.begin() ) )
{
return GossipPayloadAuthError::NOT_AN_ENVELOPE;
}
size_t offset = kGossipAuthEnvelopeMagic.size();
const size_t wire_size = static_cast<size_t>( wire_data.size() );
uint32_t key_length = 0;
uint32_t signature_length = 0;
if ( !detail::ReadU32Be( wire_data, offset, key_length )
|| wire_size - offset < static_cast<size_t>( key_length ) )
{
return GossipPayloadAuthError::MALFORMED_ENVELOPE;
}
const auto key_bytes = wire_data.subspan( offset, static_cast<size_t>( key_length ) );
offset += static_cast<size_t>( key_length );
if ( !detail::ReadU32Be( wire_data, offset, signature_length )
|| wire_size - offset < static_cast<size_t>( signature_length ) )
{
return GossipPayloadAuthError::MALFORMED_ENVELOPE;
}
const auto signature_bytes = wire_data.subspan( offset, static_cast<size_t>( signature_length ) );
offset += static_cast<size_t>( signature_length );
const auto payload = wire_data.subspan( offset );
std::vector<uint8_t> protobuf_key_bytes( key_bytes.begin(), key_bytes.end() );
auto public_key = detail::GossipAuthKeyMarshaller().unmarshalPublicKey(
libp2p::crypto::ProtobufKey{ std::move( protobuf_key_bytes ) } );
if ( public_key.has_error() )
{
return GossipPayloadAuthError::MALFORMED_ENVELOPE;
}
// Key <-> from binding (the CR-G01 core): the embedded public key must
// derive exactly the PeerId the wire from-field names.
auto key_peer_id = libp2p::peer::PeerId::fromPublicKey(
libp2p::crypto::ProtobufKey{ std::vector<uint8_t>( key_bytes.begin(), key_bytes.end() ) } );
auto from_peer_id = libp2p::peer::PeerId::fromBytes( from_bytes );
if ( key_peer_id.has_error() || from_peer_id.has_error()
|| key_peer_id.value() != from_peer_id.value() )
{
return GossipPayloadAuthError::KEY_FROM_MISMATCH;
}
const auto signable = detail::BuildSignableBytes( from_bytes, payload );
auto verified = detail::GossipAuthCryptoProvider().verify( signable, signature_bytes, public_key.value() );
if ( verified.has_error() || !verified.value() )
{
return GossipPayloadAuthError::SIGNATURE_INVALID;
}
return libp2p::outcome::success( OpenedGossipPayload{ key_peer_id.value(), payload } );
}
} // namespace sgns::base
#endif // SUPERGENIUS_BASE_GOSSIP_AUTH_HPP
Updated on 2026-10-06 at 13:34:21 +0000