ntcp2

package
v0.1.59999 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 28 Imported by: 2

README

NTCP2 Modifier Implementation

This package implements NTCP2-specific handshake modifications for the I2P transport protocol. NTCP2 is a TCP-based transport that uses the Noise Protocol Framework with specific obfuscation and padding techniques to resist traffic analysis and Deep Packet Inspection (DPI).

Features

1. AES Obfuscation Modifier

Implements AES-256-CBC obfuscation of ephemeral keys (X and Y values) in handshake messages 1 and 2.

  • AESObfuscationModifier: Encrypts/decrypts 32-byte ephemeral keys using router hash as AES key
  • Message 1: Uses published IV from network database
  • Message 2: Uses AES state from message 1 encryption
  • Message 3+: No AES obfuscation applied
// Create AES obfuscation modifier
routerHash := make([]byte, 32) // 32-byte router hash (RH_B)
iv := make([]byte, 16)         // 16-byte IV from network database
modifier, err := ntcp2.NewAESObfuscationModifier("aes_obfuscation", routerHash, iv)
2. SipHash Length Modifier

Implements SipHash-2-4 obfuscation of frame lengths in the data phase to prevent length analysis.

  • SipHashLengthModifier: Obfuscates 2-byte frame lengths using SipHash-2-4
  • Data Phase Only: Only applies to final phase (after handshake completion)
  • Symmetric XOR: Uses XOR with SipHash output for reversible obfuscation
// Create SipHash length modifier
sipKeys := [2]uint64{0x0123456789ABCDEF, 0xFEDCBA9876543210} // k1, k2
initialIV := uint64(0x1122334455667788)                      // 8-byte IV
modifier := ntcp2.NewSipHashLengthModifier("siphash_length", sipKeys, initialIV)
3. NTCP2 Padding Modifier

Implements NTCP2-specific padding strategies aligned with I2P specifications for different message phases.

  • Cleartext Padding: For messages 1 and 2 (outside AEAD frames) with cryptographically secure random padding
  • AEAD Padding: For message 3 and data phase (inside AEAD frames with type 254) using I2P block format
  • Configurable Padding Ratios: Supports I2P NTCP2 spec padding ratios (0.0 to 15.9375) for traffic analysis resistance
  • Dynamic Parameter Updates: Runtime adjustment of padding limits and ratios during connection
  • Block Parsing: I2P block format parsing with security validation
// Create padding modifiers for different phases
cleartextPadding, err := ntcp2.NewNTCP2PaddingModifier("cleartext_padding", 4, 16, false)
aeadPadding, err := ntcp2.NewNTCP2PaddingModifier("aead_padding", 0, 32, true)

// Create with specific padding ratio for traffic analysis resistance
ratioPadding, err := ntcp2.NewNTCP2PaddingModifierWithRatio("ratio_padding", 4, 64, true, 1.0) // 100% padding

// For testing only (deterministic padding - INSECURE for production)
testPadding, err := ntcp2.NewNTCP2PaddingModifierForTesting("test_padding", 4, 16, false)

Package Responsibilities

The NTCP2 protocol implementation is split between this package (go-i2p/go-noise/ntcp2) and the router transport layer (go-i2p/go-i2p/lib/transport/ntcp). Each layer has clearly defined responsibilities:

This package (go-noise/ntcp2) — Noise Protocol Layer
Responsibility Implementation
Noise XK handshake execution NTCP2Conn via NoiseConn
AES-256-CBC ephemeral key obfuscation (messages 1 & 2) AESObfuscationModifier
SipHash-2-4 frame length obfuscation (data phase) SipHashLengthModifier
SipHash key derivation from ask_master + handshake hash DeriveSipHashKeys() in kdf.go
ChaChaPoly AEAD frame encryption/decryption NTCP2Conn.Read() / Write()
Data-phase AEAD error handling (probing resistance) handleAEADError()
Frame padding (type 254 padding blocks) NTCP2PaddingModifier
Nonce management and exhaustion detection NonceExhaustionImminent() advisory (upstream CipherState enforces hard limit)
Replay cache Per-router ephemeral key (X value) cache with TTL eviction
KDF intermediate material zeroing zeroBytes() in kdf.go
Connection configuration and validation NTCP2Config
Router transport layer (go-i2p/go-i2p/lib/transport/ntcp)
Responsibility Notes
Handshake-phase probing resistance Random delay + junk read on message 1/2 AEAD failure
Encrypted termination blocks All 18 reason codes, AEAD-encrypted for graceful close
I2NP block framing Block types 0–4, 254: demuxing, parsing, serialization
Options negotiation Type 1 block: padding limits, dummy traffic, delay
Clock skew validation ±60s tolerance on messages 1 & 2 timestamps
RemoteStaticKey lookup Network database → RouterInfos= static key
RouterIdentity parsing Full RouterIdentity from message 3 part 2
Router hash computation SHA-256(RouterIdentity) via common/data.HashData()
Version detection NTCP2 version negotiation
Integration Points

The router transport layer integrates with this package through:

  • NTCP2Config — Connection configuration with handshake parameters, modifier toggles, and keys
  • NTCP2Conn — Exposes PeerStaticKey(), HandshakeHash(), SetLengthObfuscator(), and standard net.Conn interface
  • DeriveSipHashKeys() — Called by PostHandshakeHook to derive per-direction SipHash keys
  • PostHandshakeHook — Callback mechanism for post-handshake key derivation
  • AdditionalSymmetricKeyLabels{"ask"} label triggers SplitWithASK() for the ask_master secret

Integration with ConnConfig

All modifiers integrate with the existing ConnConfig builder pattern:

// Create NTCP2 modifier chain with padding
aesModifier, _ := ntcp2.NewAESObfuscationModifier("aes", routerHash, iv)
sipModifier := ntcp2.NewSipHashLengthModifier("siphash", sipKeys, initialIV)
paddingModifier, _ := ntcp2.NewNTCP2PaddingModifierWithRatio("padding", 4, 32, false, 0.5) // 50% padding ratio

// Configure connection with NTCP2 modifiers
config := noise.NewConnConfig("XK", true).
    WithModifiers(aesModifier, sipModifier, paddingModifier).
    WithHandshakeTimeout(30 * time.Second)

// Create connection with NTCP2 modifications
conn, err := noise.NewNoiseConn(underlying, config)

I2P NTCP2 Protocol Compliance

This implementation follows the I2P NTCP2 specification:

Handshake Pattern: Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256
  • Base Pattern: XK (static key known pattern)
  • Modifications: aesobfse (AES obfuscation), hs2 (handshake step 2), hs3 (handshake step 3)
  • DH Function: Curve25519 (25519)
  • Cipher: ChaCha20-Poly1305 (ChaChaPoly)
  • Hash: SHA-256 (SHA256)
Security Properties
  1. Ephemeral Key Obfuscation: Prevents DPI fingerprinting of Noise handshake patterns
  2. Length Obfuscation: SipHash masks frame lengths to resist traffic analysis
  3. Message Padding: Adds variable padding to obscure payload sizes
  4. Cryptographic Security: Uses AES-256-CBC and SipHash-2-4 algorithms

Testing

Test suite with coverage:

cd ntcp2
go test -v

Tests include:

  • Roundtrip verification (obfuscate → deobfuscate = original)
  • Phase-specific behavior validation
  • Error handling for invalid parameters
  • Integration testing with multiple modifiers

Thread Safety

All modifiers are safe for concurrent use:

  • Separate State: Outbound and inbound operations use independent state
  • No Shared Mutation: Each modifier instance maintains its own state
  • Defensive Copying: Input parameters are copied to prevent external modification

Usage Notes

Production Considerations
  1. Router Hash: Must be Bob's (the responder's) 32-byte I2P router hash (RH_B). Both initiator and responder pass RH_B to NewNTCP2Config().
  2. IV Sources: Use network database published IV for reproducible handshakes
  3. SipHash Keys: Derive from session keys using proper KDF
  4. Padding: Uses cryptographically secure random padding by default for security
  5. Padding Ratios: Configure appropriate ratios based on security/bandwidth trade-offs
  6. Block Validation: I2P block format parsing prevents protocol attacks
Protocol Extensions

The modifier system supports additional NTCP2 extensions:

  • Custom obfuscation patterns
  • Dynamic padding strategies
  • Protocol version negotiation

This implementation provides a foundation for I2P NTCP2 transport while maintaining the security guarantees of the Noise Protocol Framework.

Documentation

Overview

Package ntcp2 provides NTCP2-specific implementations for the Noise Protocol Framework supporting I2P's NTCP2 transport protocol with router identity and session management.

Index

Constants

View Source
const (
	// RouterHashSize is the size of the I2P router identity hash in bytes.
	RouterHashSize = 32

	// StaticKeySize is the size of a Curve25519 static key in bytes.
	StaticKeySize = 32

	// IVSize is the size of the AES-CBC initialization vector in bytes.
	IVSize = 16

	// PaddingBlockType is the I2P NTCP2 padding block type identifier.
	PaddingBlockType = 254

	// MaxBlockDataSize is the maximum data size for a single I2P NTCP2 block (bytes).
	MaxBlockDataSize = 65516

	// MaxFrameSize is the maximum size of an NTCP2 data frame (bytes).
	MaxFrameSize = 65535

	// SpecMaxFrameSize is the absolute maximum frame size allowed by the I2P spec (uint16 max).
	// validateFrameConfiguration uses this to reject user-provided values that exceed the wire limit.
	SpecMaxFrameSize = 65535

	// MinDataPhaseFrameSize is the minimum valid data-phase frame size per the I2P spec.
	// The spec states the deobfuscated size range is 16–65535. The minimum corresponds
	// to a ChaChaPoly ciphertext containing only the 16-byte Poly1305 MAC tag (empty payload).
	MinDataPhaseFrameSize = 16

	// BlockHeaderSize is the size of an I2P block header: [type:1][size:2].
	BlockHeaderSize = 3

	// SipHashIVSize is the size of the SipHash IV in bytes (uint64 = 8 bytes).
	SipHashIVSize = 8

	// NTCP2ProtocolName is the full Noise protocol name for NTCP2 as defined by the I2P spec.
	// This is passed to InitializeSymmetric() via the ProtocolName field on noise.Config,
	// producing the correct KDF output for interoperability with other I2P implementations.
	NTCP2ProtocolName = "Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256"

	// NTCP2Pattern is the base Noise pattern used by NTCP2.
	NTCP2Pattern = "XK"

	// DefaultMaxFrameSize is the default maximum frame size (16KB).
	DefaultMaxFrameSize = 16384

	// DefaultMaxPaddingSize is the default maximum padding size in bytes.
	DefaultMaxPaddingSize = 64

	// MaxNTCP2HandshakePadding is the maximum allowed cleartext padding size
	// during the NTCP2 handshake (bytes). Per spec §4.3, padding is "0..223 bytes"
	// in practice. We set a conservative limit of 1024 to allow for future spec
	// changes while preventing DoS via unbounded allocation on malicious padLen.
	MaxNTCP2HandshakePadding = 1024

	// MaxNTCP2Message3Part2Len is the maximum allowed size for message 3 part 2
	// (Alice's RouterInfo block plus optional padding/options). Per spec, RouterInfo
	// is typically < 2 KB; we allow up to 8192 bytes (8 KB) for legitimate RouterInfo
	// plus generous padding headroom, while preventing DoS via unbounded allocation
	// on malicious m3p2Len. This limit applies to the AEAD ciphertext size.
	MaxNTCP2Message3Part2Len = 8192

	// MaxPaddingRatio is the maximum padding ratio per I2P NTCP2 spec (4.4 fixed-point).
	MaxPaddingRatio = 15.9375

	// Poly1305Overhead is the ChaChaPoly AEAD authentication tag size in bytes.
	Poly1305Overhead = 16

	// MaxNonce is the nonce limit per the Noise Protocol spec and I2P NTCP2 spec.
	// Connections MUST be terminated before the nonce reaches 2^64 - 2.
	// Using 2^64 - 2 = 18446744073709551614.
	MaxNonce uint64 = 18446744073709551614

	// AEADErrorMaxJunkBytes is the maximum number of random bytes to read
	// on an AEAD authentication failure for probing resistance. Per the spec:
	// "random number of bytes (range TBD)" — we use 1024 as a reasonable upper bound.
	//
	// INVARIANT: AEADErrorMaxJunkBytes MUST be a power of two.
	// The bitmask in handleAEADError (val & (AEADErrorMaxJunkBytes - 1)) only
	// produces a uniform distribution when this constant is a power of two.
	// Changing it to a non-power-of-two value will introduce modulo bias.
	AEADErrorMaxJunkBytes = 1024

	// AEADErrorTimeoutMin is the minimum duration to wait while reading random
	// bytes on an AEAD authentication failure. Per the spec: "random timeout
	// (range TBD)" — randomized over [1s, 3s] to avoid timing fingerprints.
	AEADErrorTimeoutMin = 1 * time.Second

	// AEADErrorTimeoutMax is the maximum duration to wait while reading random
	// bytes on an AEAD authentication failure.
	AEADErrorTimeoutMax = 3 * time.Second

	// NonceRekeyThreshold is the nonce value at which the connection should
	// be considered approaching exhaustion. Since Noise Rekey() does not reset
	// the nonce counter, the correct response is to establish a new connection.
	// Set to MaxNonce - 1000 to provide advance warning.
	NonceRekeyThreshold = MaxNonce - 1000
)
View Source
const ClockSkewTolerance = 60 * time.Second

ClockSkewTolerance is the maximum allowed difference between local and remote clocks for NTCP2 handshake timestamp validation. Per the I2P spec, connections with clock skew exceeding this value should be rejected.

View Source
const DefaultHandshakeRetries = 3

DefaultHandshakeRetries is the default number of handshake retry attempts.

View Source
const DefaultHandshakeTimeoutSeconds = 30

DefaultHandshakeTimeoutSeconds is the default handshake timeout in seconds.

View Source
const FrameLengthFieldSize = 2

FrameLengthFieldSize is the size of the SipHash-obfuscated length field.

Variables

This section is empty.

Functions

func DeriveSipHashKeys added in v0.1.6

func DeriveSipHashKeys(askMaster, handshakeHash []byte) (
	sipKeysAB [2]uint64, sipIVAB uint64,
	sipKeysBA [2]uint64, sipIVBA uint64,
	err error,
)

DeriveSipHashKeys derives per-direction SipHash-2-4 keys and initial IVs from the Noise handshake hash and ask_master secret per the I2P NTCP2 spec.

The derivation follows the spec's 5-step HMAC-SHA256 chain:

Step 1: temp_key   = HMAC-SHA256(key=ask_master, data=h || "siphash")
Step 2: sip_master = HMAC-SHA256(key=temp_key,   data=byte(0x01))
Step 3: temp_key   = HMAC-SHA256(key=sip_master, data=zerolen)
Step 4: sipkeys_ab = HMAC-SHA256(key=temp_key,   data=byte(0x01))[0:24]
Step 5: sipkeys_ba = HMAC-SHA256(key=temp_key,   data=sipkeys_ab[0:32] || byte(0x02))[0:24]

NOTE: Step 5 uses the full 32-byte HMAC output from step 4 as its input prefix, not just the 24 bytes extracted for sipkeys_ab. This matches the i2pd reference implementation (m_Sipkeysab is 32 bytes; m_Sipkeysab[32]=2 before HMAC call).

Each 24-byte output is split into (sipk1, sipk2, sipiv) as little-endian uint64s.

Parameters:

  • askMaster: the ask_master secret from the Noise handshake (32 bytes)
  • handshakeHash: the handshake hash (h) from the completed Noise session (32 bytes)

Returns:

  • sipKeysAB: [2]uint64{sipk1, sipk2} for direction A→B (initiator→responder)
  • sipIVAB: initial IV for A→B SipHash length obfuscation chain
  • sipKeysBA: [2]uint64{sipk1, sipk2} for direction B→A (responder→initiator)
  • sipIVBA: initial IV for B→A SipHash length obfuscation chain
  • err: non-nil if derivation fails

Types

type AESObfuscationModifier

type AESObfuscationModifier struct {
	// contains filtered or unexported fields
}

AESObfuscationModifier implements NTCP2's AES-based ephemeral key obfuscation. This modifier encrypts/decrypts the X and Y ephemeral keys in messages 1 and 2 using AES-256-CBC with the router hash as key and published IV.

Per the NTCP2 spec, the AES state (last ciphertext block) from message 1 encryption is carried forward as the IV for message 2 encryption.

After AES decryption, received keys are validated per the NTCP2 spec: X[31]&0x80 must be 0 (Curve25519 requirement). Invalid keys cause message rejection.

func NewAESObfuscationModifier

func NewAESObfuscationModifier(name string, routerHash, iv []byte) (*AESObfuscationModifier, error)

NewAESObfuscationModifier creates a new AES obfuscation modifier for NTCP2. routerHash must be 32 bytes (RH_B), iv must be 16 bytes from network database.

func (*AESObfuscationModifier) Clone added in v0.1.6

Clone creates a deep copy of the AESObfuscationModifier with independent state. This implements handshake.ModifierCloner to support safe Config.Clone() operations. The cloned modifier starts with a fresh state (no aesState carried over).

func (*AESObfuscationModifier) Close added in v0.1.6

func (aom *AESObfuscationModifier) Close() error

Close zeroes the AES key material and IV stored in the modifier to prevent sensitive data from lingering in memory after the connection is closed. This method is safe for concurrent use.

func (*AESObfuscationModifier) ModifyInbound

func (aom *AESObfuscationModifier) ModifyInbound(phase handshake.HandshakePhase, data []byte) ([]byte, error)

ModifyInbound removes AES obfuscation from ephemeral keys in handshake messages.

The modifier chain receives the full Noise message (e.g. 64 bytes for msg1/msg2: 32-byte ephemeral key + AEAD payload). Only the first 32 bytes (the ephemeral key) are AES-CBC decrypted; the remainder is passed through unchanged.

func (*AESObfuscationModifier) ModifyOutbound

func (aom *AESObfuscationModifier) ModifyOutbound(phase handshake.HandshakePhase, data []byte) ([]byte, error)

ModifyOutbound applies AES obfuscation to ephemeral keys in handshake messages. For message 1: encrypts X key with RH_B and published IV For message 2: encrypts Y key with RH_B and AES state from message 1

The modifier chain receives the full Noise message (e.g. 64 bytes for msg1/msg2: 32-byte ephemeral key + AEAD payload). Only the first 32 bytes (the ephemeral key) are AES-CBC encrypted; the remainder is passed through unchanged.

func (*AESObfuscationModifier) Name

func (aom *AESObfuscationModifier) Name() string

Name returns the modifier name for logging and debugging.

type Acceptor added in v0.1.6

type Acceptor interface {
	net.Listener
	// AcceptWithHandshake accepts the next inbound connection and performs
	// the NTCP2 handshake before returning. The ctx governs handshake
	// cancellation for each accepted connection.
	AcceptWithHandshake(ctx context.Context) (ConnIface, error)
}

Acceptor accepts inbound NTCP2 connections on a listener. It extends net.Listener with AcceptWithHandshake, which performs the NTCP2 handshake as part of the accept loop. *Listener satisfies this interface.

type Addr added in v0.1.6

type Addr struct {
	// contains filtered or unexported fields
}

Addr implements net.Addr for NTCP2 transport connections. It provides I2P-specific addressing information including router identity, destination hash, and session parameters for the NTCP2 protocol.

func NewNTCP2Addr

func NewNTCP2Addr(underlying net.Addr, routerHash data.Hash, role string) (*Addr, error)

NewNTCP2Addr creates a new NTCP2Addr with the specified TCP address and router hash. routerHash is the I2P router identity hash. role should be either "initiator" or "responder".

func (*Addr) IdentHash added in v0.1.6

func (na *Addr) IdentHash() [32]byte

IdentHash returns the router identity hash as a fixed-size [32]byte array.

func (*Addr) Network added in v0.1.6

func (na *Addr) Network() string

Network returns "ntcp2" to identify this as an NTCP2 transport address. This implements the net.Addr interface requirement.

func (*Addr) Role added in v0.1.6

func (na *Addr) Role() string

Role returns the connection role ("initiator" or "responder").

func (*Addr) RouterHash added in v0.1.6

func (na *Addr) RouterHash() data.Hash

RouterHash returns the router identity hash.

func (*Addr) SetRouterHash added in v0.1.6

func (na *Addr) SetRouterHash(routerHash data.Hash)

SetRouterHash updates the router identity hash. This is used to update a placeholder zero hash after the Noise handshake reveals the remote peer's static key.

func (*Addr) String added in v0.1.6

func (na *Addr) String() string

String returns a string representation of the NTCP2 address. Format: "ntcp2://[router_hash]/[role]/[tcp_address][?dest=dest_hash]" Router hash and optional parameters are base64 encoded for readability.

func (*Addr) UnderlyingAddr added in v0.1.6

func (na *Addr) UnderlyingAddr() net.Addr

UnderlyingAddr returns the underlying TCP network address.

type Config added in v0.1.6

type Config struct {
	// Pattern is the Noise protocol pattern for NTCP2
	// Default: "XK" (standard NTCP2 pattern)
	Pattern string

	// Initiator indicates if this connection is the handshake initiator
	// For listeners, this is always false
	Initiator bool

	// BobRouterHash is Bob's (the responder's) router hash.
	// Per the I2P NTCP2 spec, both Alice (initiator) and Bob (responder)
	// use RH_B as the AES-256-CBC key for ephemeral key obfuscation.
	// For the responder, this is their own router hash.
	// For the initiator, this is the remote peer's router hash.
	// Required for NTCP2 addressing and AES obfuscation.
	BobRouterHash data.Hash

	// StaticKey is the long-term static key for this peer (32 bytes for Curve25519)
	StaticKey []byte

	// RemoteRouterHash is the remote peer's router identity hash.
	// Required for outbound connections, optional for listeners.
	RemoteRouterHash *data.Hash

	// RemoteStaticKey is the remote peer's Curve25519 static public key (32 bytes)
	// Required for initiator connections using XK pattern (pre-message: ← s)
	// Distinct from RemoteRouterHash (which is SHA-256 of RouterIdentity)
	RemoteStaticKey []byte

	// HandshakeTimeout is the maximum time to wait for handshake completion
	// Default: 30 seconds
	HandshakeTimeout time.Duration

	// ReadTimeout is the timeout for read operations after handshake
	// Default: no timeout (0)
	ReadTimeout time.Duration

	// WriteTimeout is the timeout for write operations after handshake
	// Default: no timeout (0)
	WriteTimeout time.Duration

	// HandshakeRetries is the number of handshake retry attempts
	// Default: 3 attempts (0 = no retries, -1 = infinite retries)
	HandshakeRetries int

	// RetryBackoff is the base delay between retry attempts
	// Actual delay uses exponential backoff: delay = RetryBackoff * (2^attempt)
	// Default: 1 second
	RetryBackoff time.Duration

	// EnableAESObfuscation enables AES-based ephemeral key obfuscation
	// Default: true (recommended for production)
	EnableAESObfuscation bool

	// ObfuscationIV is the 16-byte IV for AES obfuscation
	// If nil, will be derived from router hash (recommended)
	ObfuscationIV []byte

	// EnableSipHashLength enables SipHash-based frame length obfuscation
	// Default: true (recommended for production)
	EnableSipHashLength bool

	// SipHashKeys are the k1, k2 keys for SipHash length obfuscation
	// If empty, will be derived during handshake
	SipHashKeys [2]uint64

	// Modifiers is a list of additional handshake modifiers for custom obfuscation
	// These are applied in addition to NTCP2's standard modifiers
	// Default: empty (no additional modifiers)
	Modifiers []handshake.HandshakeModifier

	// MaxFrameSize is the maximum size of NTCP2 data frames
	// Default: 16384 bytes (16KB)
	MaxFrameSize int

	// FramePaddingEnabled enables random padding in NTCP2 frames
	// Default: true (recommended for traffic analysis resistance)
	FramePaddingEnabled bool

	// MinPaddingSize is the minimum padding size for frames
	// Default: 0 bytes
	MinPaddingSize int

	// MaxPaddingSize is the maximum padding size for frames
	// Default: 64 bytes
	MaxPaddingSize int

	// LocalRouterInfo contains Alice's serialized RouterInfo bytes.
	// Required for outbound NTCP2 connections: its length determines m3p2Len
	// in the message 1 options block and it is sent as the encrypted payload
	// in message 3 part 2.
	LocalRouterInfo []byte

	// StrictRouterInfoVerification, when true, causes the initiator handshake
	// to return a hard error if the configured LocalRouterInfo does not
	// advertise the static key that will be sent in the Noise handshake msg1.
	// Defaults to false so that existing tests using synthetic RouterInfo
	// bytes continue to work. Set to true in production to catch
	// misconfiguration before a silent i2pd interop failure (frame #0 EOF).
	StrictRouterInfoVerification bool

	// ReplayDetector checks for replayed ephemeral keys during handshakes.
	// If nil, replay detection is disabled (not recommended for production).
	// Shared across all responder connections using this config (typically
	// instantiated once per listener). Initiator connections ignore this field.
	ReplayDetector ReplayDetector
	// contains filtered or unexported fields
}

Config contains configuration for creating NTCP2 connections and listeners. It follows the builder pattern for optional configuration and validation, similar to the main ConnConfig but with NTCP2-specific parameters.

func NewNTCP2Config

func NewNTCP2Config(bobRouterHash data.Hash, initiator bool) (*Config, error)

NewNTCP2Config creates a new NTCP2Config with sensible defaults. bobRouterHash is Bob's (the responder's) router hash (RH_B). Per the NTCP2 spec, both initiator and responder use RH_B for AES ephemeral key obfuscation. For a responder, pass your own router hash; for an initiator, pass the remote peer's router hash. initiator indicates whether this connection will initiate the handshake.

func (*Config) Clone added in v0.1.6

func (nc *Config) Clone() *Config

Clone creates a deep copy of this NTCP2Config that is safe to use independently (e.g., for per-connection configs on the listener path). The atomic.Pointer[SipHashLengthModifier] field is NOT copied — the returned config has a fresh zero-value atomic, which is correct because the PostHandshakeHook will populate it after the handshake.

IMPORTANT: The Modifiers slice is shallow-copied — individual modifier interface values (pointers to AESObfuscationModifier, SipHashLengthModifier, etc.) are shared between the original and clone. Since modifiers carry mutable state (AES cipher state, SipHash IVs), the clone MUST NOT be used concurrently with the original for handshakes. Callers MUST call ToConnConfig() on the clone before use, which creates fresh modifier instances with independent state. The listener path does this correctly. Direct use of Clone() without ToConnConfig() will cause data races and corrupt handshake state if both configs are active concurrently.

func (*Config) SipHashModifier added in v0.1.6

func (nc *Config) SipHashModifier() *SipHashLengthModifier

SipHashModifier returns the SipHash length modifier created during ToConnConfig().

PRECONDITION: This method returns nil until the PostHandshakeHook has executed during Handshake(). Callers MUST NOT call this method before the handshake completes, as the modifier's keys are derived from the handshake state. Calling PropagateSipHash() before the handshake will return an error rather than silently no-op.

Returns nil if SipHash length obfuscation is disabled or ToConnConfig() hasn't been called. Each call to ToConnConfig() creates a fresh modifier instance, so configs can be safely reused for multiple connections without sharing IV state.

func (*Config) ToConnConfig added in v0.1.6

func (nc *Config) ToConnConfig() (*noise.ConnConfig, error)

ToConnConfig converts NTCP2Config to a standard ConnConfig for use with NoiseConn. This includes setting up NTCP2-specific modifiers based on the configuration. A PostHandshakeHook is automatically registered when SipHash length obfuscation is enabled — the hook captures the handshake hash for future SipHash key derivation.

func (*Config) Validate added in v0.1.6

func (nc *Config) Validate() error

Validate checks if the configuration is valid for NTCP2.

func (*Config) WithAESObfuscation added in v0.1.6

func (nc *Config) WithAESObfuscation(enabled bool, customIV []byte) *Config

WithAESObfuscation enables or disables AES-based ephemeral key obfuscation. When enabled with a custom IV, the IV must be exactly 16 bytes; invalid IV lengths are ignored. Call Validate() to check all fields before use. Note: Options negotiation (padding limits as 4.4 fixed-point, dummy traffic, delay parameters) is the responsibility of the higher-level router transport.

func (*Config) WithFrameSettings added in v0.1.6

func (nc *Config) WithFrameSettings(maxSize int, paddingEnabled bool, minPadding, maxPadding int) *Config

WithFrameSettings configures NTCP2 frame handling parameters. maxSize sets the maximum frame size (default: 16384 bytes). paddingEnabled enables random padding (default: true). minPadding and maxPadding set the padding size range (default: 0-64 bytes).

func (*Config) WithHandshakeRetries added in v0.1.6

func (nc *Config) WithHandshakeRetries(retries int) *Config

WithHandshakeRetries sets the number of handshake retry attempts. Use 0 for no retries, -1 for infinite retries.

func (*Config) WithHandshakeTimeout added in v0.1.6

func (nc *Config) WithHandshakeTimeout(timeout time.Duration) *Config

WithHandshakeTimeout sets the handshake timeout.

func (*Config) WithLocalRouterInfo added in v0.1.6

func (nc *Config) WithLocalRouterInfo(ri []byte) *Config

WithLocalRouterInfo sets Alice's serialized RouterInfo bytes for outbound NTCP2 connections. The length is used to populate m3p2Len in the message 1 options block; the bytes are encrypted and sent as message 3 part 2.

func (*Config) WithModifiers added in v0.1.6

func (nc *Config) WithModifiers(modifiers ...handshake.HandshakeModifier) *Config

WithModifiers sets additional handshake modifiers for custom obfuscation. These are applied in addition to NTCP2's standard modifiers.

func (*Config) WithPattern added in v0.1.6

func (nc *Config) WithPattern(pattern string) *Config

WithPattern sets the Noise protocol pattern. For NTCP2, this should typically remain "XK".

func (*Config) WithReadTimeout added in v0.1.6

func (nc *Config) WithReadTimeout(timeout time.Duration) *Config

WithReadTimeout sets the read timeout for post-handshake operations.

func (*Config) WithRemoteRouterHash added in v0.1.6

func (nc *Config) WithRemoteRouterHash(hash data.Hash) *Config

WithRemoteRouterHash sets the remote peer's router identity. hash must be 32 bytes. Required for outbound connections. Invalid lengths are ignored; call Validate() to check all fields before use.

func (*Config) WithRemoteStaticKey added in v0.1.6

func (nc *Config) WithRemoteStaticKey(key []byte) *Config

WithRemoteStaticKey sets the remote peer's Curve25519 static public key. key must be exactly 32 bytes. Required for initiator connections using the XK pattern, where the initiator must know the responder's static key as a pre-message (← s). This is distinct from RemoteRouterHash (which is SHA-256 of the RouterIdentity). Invalid lengths are ignored; call Validate() to check all fields before use.

func (*Config) WithReplayDetector added in v0.1.6

func (nc *Config) WithReplayDetector(detector ReplayDetector) *Config

WithReplayDetector sets the replay detector for responder handshakes. If nil, replay detection is disabled (not recommended for production). The detector is shared across all responder connections using this config (typically instantiated once per listener). Initiator connections ignore this field.

func (*Config) WithRetryBackoff added in v0.1.6

func (nc *Config) WithRetryBackoff(backoff time.Duration) *Config

WithRetryBackoff sets the base delay between retry attempts.

func (*Config) WithSipHashLength added in v0.1.6

func (nc *Config) WithSipHashLength(enabled bool, k1, k2 uint64) *Config

WithSipHashLength enables or disables SipHash-based frame length obfuscation. When enabled with custom keys, both k1 and k2 must be provided.

func (*Config) WithStaticKey added in v0.1.6

func (nc *Config) WithStaticKey(key []byte) *Config

WithStaticKey sets the static key for this connection. key must be 32 bytes for Curve25519. Invalid lengths are ignored; call Validate() to check all fields before use.

func (*Config) WithStrictRouterInfoVerification added in v0.1.6

func (nc *Config) WithStrictRouterInfoVerification(strict bool) *Config

WithStrictRouterInfoVerification enables or disables hard-error mode for the local RouterInfo / static-key mismatch check. Set to true in production to catch misconfiguration before a silent i2pd interop failure.

func (*Config) WithWriteTimeout added in v0.1.6

func (nc *Config) WithWriteTimeout(timeout time.Duration) *Config

WithWriteTimeout sets the write timeout for post-handshake operations.

type Conn added in v0.1.6

type Conn struct {

	// OnAEADError is an optional callback invoked during AEAD failure handling,
	// after the probing-resistance junk-read phase but before the TCP RST.
	// The router transport layer can use this to send a termination block
	// (type 4, reason 4 = "AEAD failure") before the connection is killed.
	// The callback receives the underlying net.Conn for direct writing;
	// the NTCP2Conn's broken flag is already set, so normal Write() is blocked.
	// If nil, no termination block is sent (current behaviour).
	OnAEADError func(conn net.Conn)
	// contains filtered or unexported fields
}

Conn implements net.Conn for NTCP2 transport connections. It wraps a NoiseConn with NTCP2-specific addressing and protocol handling.

This package implements the Noise XK handshake with NTCP2 extensions (AES ephemeral key obfuscation, SipHash frame length obfuscation, and cleartext padding). It provides framed I/O (SipHash-obfuscated length prefix + ChaChaPoly AEAD) and probing resistance on AEAD failures.

Higher-level concerns — I2NP message parsing, block framing (types 0–9), termination blocks, options negotiation, timestamp validation, replay caches, and version detection — belong in the router transport layer (github.com/go-i2p/go-i2p/lib/transport/ntcp).

func DialNTCP2

func DialNTCP2(network, addr string, config *Config) (*Conn, error)

DialNTCP2 creates a connection to the given address and wraps it with NTCP2Conn. This is a convenience function that combines net.Dial, NoiseConn creation, and NTCP2 wrapping. For more control over the underlying connection, use net.Dial followed by NewNoiseConn and NewNTCP2Conn.

func DialNTCP2WithHandshake

func DialNTCP2WithHandshake(network, addr string, config *Config) (*Conn, error)

DialNTCP2WithHandshake creates a connection and performs the NTCP2 handshake automatically. This is a convenience function that combines DialNTCP2 and handshake execution.

func DialNTCP2WithHandshakeContext

func DialNTCP2WithHandshakeContext(ctx context.Context, network, addr string, config *Config) (*Conn, error)

DialNTCP2WithHandshakeContext creates a connection and performs the NTCP2 handshake with context. The context can be used to cancel the dial or handshake operations.

func NewNTCP2Conn

func NewNTCP2Conn(noiseConn *noise.NoiseConn, localAddr, remoteAddr *Addr) (*Conn, error)

NewNTCP2Conn creates a new NTCP2Conn wrapping the provided NoiseConn. The NoiseConn must already be configured with appropriate NTCP2 modifiers.

func WrapNTCP2Conn

func WrapNTCP2Conn(conn net.Conn, config *Config) (*Conn, error)

WrapNTCP2Conn wraps an existing net.Conn with NTCP2Conn. This function creates the necessary Noise wrapper and NTCP2 addressing.

func (*Conn) Close added in v0.1.6

func (nc *Conn) Close() error

Close implements net.Conn.Close. Closes the underlying Noise connection, zeroes key material, and cleans up resources. Close is idempotent — calling it multiple times is safe.

func (*Conn) Handshake added in v0.1.6

func (c *Conn) Handshake(ctx context.Context) error

Handshake performs the NTCP2 XK three-way handshake with correct wire framing.

The standard Noise framing adds a 2-byte length prefix to every handshake message. The NTCP2 spec explicitly forbids length prefixes on messages 1, 2, and 3. This method writes and reads raw Noise bytes directly over the TCP socket to produce the correct on-wire format:

  • Message 1 (64 bytes): [AES-obfuscated e (32B)] [EncryptAndHash(options) (16B)] [tag (16B)]
  • Message 2 (64 bytes): [AES-obfuscated e (32B)] [EncryptAndHash(options) (16B)] [tag (16B)]
  • Message 3 (48 + m3p2Len bytes): [EncryptAndHash(s) (48B)] [EncryptAndHash(RI block) (m3p2Len B)]

Cleartext padding: Alice sends none (padLen=0). Bob's padding is read, MixHash'd, and discarded per the NTCP2 spec §4.4.

Handshake must not be called concurrently on the same connection.

func (*Conn) HandshakeHash added in v0.1.6

func (nc *Conn) HandshakeHash() []byte

HandshakeHash returns the Noise handshake hash (h) from the completed session. This is needed by the router transport layer to derive data-phase keys via DeriveSipHashKeys(ask_master, h) for SipHash frame length obfuscation. Returns nil if the handshake has not been initiated.

func (*Conn) LocalAddr added in v0.1.6

func (nc *Conn) LocalAddr() net.Addr

LocalAddr implements net.Conn.LocalAddr. Returns the NTCP2-specific local address.

func (*Conn) NonceExhaustionImminent added in v0.1.6

func (nc *Conn) NonceExhaustionImminent() bool

NonceExhaustionImminent returns true if either the read or write nonce counter has reached NonceRekeyThreshold, indicating that the connection is approaching the maximum nonce limit and should be replaced.

The Noise Protocol's Rekey() operation does NOT reset nonce counters, so the correct response to imminent exhaustion is to establish a new connection rather than attempt a rekey.

func (*Conn) PeerMessage3Payload added in v0.1.6

func (nc *Conn) PeerMessage3Payload() []byte

PeerMessage3Payload returns the decrypted plaintext of NTCP2 message 3 part 2 received from the remote peer. This is meaningful only on the responder side of a completed inbound handshake; it returns nil for initiator connections and before Handshake() succeeds.

The payload is the I2NP block frame as transmitted by Alice. Per the NTCP2 spec it contains a RouterInfo block (type 2) and may contain optional padding (type 254) and options (type 1) blocks. The router transport layer is responsible for parsing this and storing Alice's RouterInfo in the local NetDB / peer cache so that direct replies (e.g. ShortTunnelBuildReply for 1-hop outbound tunnels) can be routed back to her NTCP2 address.

The returned slice is a defensive copy and may be modified freely.

func (*Conn) PeerRouterInfoBytes added in v0.1.6

func (nc *Conn) PeerRouterInfoBytes() []byte

PeerRouterInfoBytes is a convenience wrapper around PeerMessage3Payload that locates the RouterInfo block (type 2) inside the message-3 part-2 frame and returns just the inner RouterInfo bytes (with the 1-byte flag field stripped). Returns nil if no payload was captured, the payload is malformed, or no RouterInfo block is present.

Block frame layout (per NTCP2 spec §5):

byte 0    : block type
bytes 1-2 : block size (uint16, big-endian) — number of bytes that follow
bytes 3+  : block data (size bytes)

For the RouterInfo block (type 2) the first data byte is a flag field; the remaining bytes are the serialized RouterInfo.

func (*Conn) PeerStaticKey added in v0.1.6

func (nc *Conn) PeerStaticKey() []byte

PeerStaticKey returns the remote peer's Noise static public key (32 bytes). This is available after the handshake completes and can be used by the router transport layer (github.com/go-i2p/go-i2p/lib/transport/ntcp) to compute the full router hash via SHA-256(RouterIdentity) using github.com/go-i2p/common/router_identity.

func (*Conn) PropagatePeerStaticKey added in v0.1.6

func (nc *Conn) PropagatePeerStaticKey()

PropagatePeerStaticKey extracts the remote peer's Noise static public key from the completed handshake and updates the remote NTCP2 address's router hash. This must be called after a successful Handshake() to replace the placeholder zero hash that was used before the peer's identity was known (e.g., on inbound/responder connections).

If the peer static key is not available (handshake not completed) or the remote address already has a non-zero router hash, this is a no-op.

func (*Conn) PropagateSipHash added in v0.1.6

func (nc *Conn) PropagateSipHash() error

PropagateSipHash copies the SipHash modifier from the stored NTCP2Config (populated by the PostHandshakeHook during Handshake) into this connection's lengthObfuscator. Call this immediately after a successful Handshake().

Returns an error if: - No NTCP2 config is stored (SetNTCP2Config was not called) - The SipHash modifier is nil (PostHandshakeHook has not yet executed or SipHash is disabled)

This prevents silent no-op behavior when PropagateSipHash is called before the handshake completes.

func (*Conn) Read added in v0.1.6

func (nc *Conn) Read(b []byte) (int, error)

Read implements net.Conn.Read. When a length obfuscator is set, reads use NTCP2 framed I/O:

  1. Return any buffered plaintext from a previous frame first
  2. Read exactly 2 bytes from the underlying TCP connection
  3. XOR with SipHash inbound mask to recover the frame length
  4. Read exactly frameLength bytes of ciphertext from TCP
  5. Decrypt the ciphertext via the Noise cipher state
  6. Copy plaintext into the caller's buffer; buffer any remainder

When no length obfuscator is set, delegates directly to NoiseConn.Read.

func (*Conn) Rekey added in v0.1.6

func (nc *Conn) Rekey() error

Rekey triggers a rekey operation on the underlying Noise connection. This delegates to NoiseConn.Rekey(), which advances the encryption key material per the Noise Protocol specification.

This method allows NTCP2Conn to satisfy a Rekeyer interface:

type Rekeyer interface { Rekey() error }

func (*Conn) RemoteAddr added in v0.1.6

func (nc *Conn) RemoteAddr() net.Addr

RemoteAddr implements net.Conn.RemoteAddr. Returns the NTCP2-specific remote address.

func (*Conn) Role added in v0.1.6

func (nc *Conn) Role() string

Role returns the connection role (initiator or responder).

func (*Conn) RouterHash added in v0.1.6

func (nc *Conn) RouterHash() data.Hash

RouterHash returns the router hash from the remote address. This is I2P-specific functionality for NTCP2 connections.

func (*Conn) SetDeadline added in v0.1.6

func (nc *Conn) SetDeadline(t time.Time) error

SetDeadline implements net.Conn.SetDeadline. Sets read and write deadlines on the underlying connection.

func (*Conn) SetLengthObfuscator added in v0.1.6

func (nc *Conn) SetLengthObfuscator(slm *SipHashLengthModifier)

SetLengthObfuscator sets the SipHash length obfuscator for data-phase framing. When set, Read/Write will use framed I/O with SipHash-obfuscated length prefixes. This is safe to call concurrently with Read/Write (uses atomic.Pointer).

func (*Conn) SetNTCP2Config added in v0.1.6

func (nc *Conn) SetNTCP2Config(cfg *Config)

SetNTCP2Config stores a reference to the originating NTCP2Config so that PropagateSipHash can copy PostHandshakeHook-derived keys after handshake. This is safe to call concurrently with PropagateSipHash (uses atomic.Pointer).

func (*Conn) SetReadDeadline added in v0.1.6

func (nc *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline implements net.Conn.SetReadDeadline. Sets the read deadline on the underlying connection.

func (*Conn) SetWriteDeadline added in v0.1.6

func (nc *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline implements net.Conn.SetWriteDeadline. Sets the write deadline on the underlying connection.

func (*Conn) UnderlyingConn added in v0.1.6

func (nc *Conn) UnderlyingConn() *noise.NoiseConn

UnderlyingConn returns the underlying NoiseConn for advanced operations. This allows access to Noise-specific functionality when needed.

func (*Conn) Write added in v0.1.6

func (nc *Conn) Write(b []byte) (int, error)

Write implements net.Conn.Write. When a length obfuscator is set, writes use NTCP2 framed I/O:

  1. Encrypt the plaintext via the Noise cipher state
  2. Compute the ciphertext length as a uint16
  3. XOR with SipHash outbound mask to obfuscate the length
  4. Write [2-byte obfuscated length][ciphertext] to the underlying TCP connection

When no length obfuscator is set, delegates directly to NoiseConn.Write. Large writes are transparently split into multiple frames of at most MaxFrameSize minus Poly1305Overhead bytes of plaintext each.

type ConnIface added in v0.1.6

type ConnIface interface {
	net.Conn
	// Handshake performs the NTCP2 handshake. Must be called before Read/Write.
	Handshake(ctx context.Context) error
}

ConnIface is the minimal interface satisfied by *Conn. Dialer.DialContext and Acceptor.AcceptWithHandshake return ConnIface so that callers can substitute test doubles without importing the concrete *Conn type. Where NTCP2-specific methods (e.g. PropagateSipHash, RouterHash) are needed, use a type assertion: conn.(*Conn).PropagateSipHash().

type Dialer added in v0.1.6

type Dialer interface {
	// DialContext dials an outbound NTCP2 connection to network/addr using
	// config, performs the NTCP2 handshake, and returns the established Conn.
	// The ctx governs handshake cancellation.
	DialContext(ctx context.Context, network, addr string, config *Config) (ConnIface, error)
}

Dialer establishes outbound NTCP2 connections with handshake. The package-level DialNTCP2WithHandshakeContext provides this behaviour; wrap it via NewDialer() to obtain a Dialer value suitable for dependency injection and test substitution.

func NewDialer added in v0.1.6

func NewDialer() Dialer

NewDialer returns a Dialer backed by DialNTCP2WithHandshakeContext. The returned value may be stored in a Dialer field and replaced by a test double without changing call sites.

type Listener added in v0.1.6

type Listener struct {
	// contains filtered or unexported fields
}

Listener implements net.Listener for accepting NTCP2 transport connections. It accepts raw TCP connections from the underlying listener, wraps each in a NoiseConn created via NTCP2Config.ToConnConfig() (which sets the correct CipherSuite, ProtocolName, and Modifiers), and then wraps that in an NTCP2Conn.

func ListenNTCP2

func ListenNTCP2(network, addr string, config *Config) (*Listener, error)

ListenNTCP2 creates a listener on the given address and wraps it with NTCP2Listener. This is a convenience function that combines net.Listen and NewNTCP2Listener. For more control over the underlying listener, use net.Listen followed by NewNTCP2Listener.

func NewNTCP2Listener

func NewNTCP2Listener(underlying net.Listener, config *Config) (*Listener, error)

NewNTCP2Listener creates a new NTCP2Listener that wraps the underlying TCP listener. The listener will accept connections and wrap them in NTCP2Conn instances configured as responders with NTCP2-specific addressing and protocol handling.

func WrapNTCP2Listener

func WrapNTCP2Listener(listener net.Listener, config *Config) (*Listener, error)

WrapNTCP2Listener wraps an existing net.Listener with NTCP2Listener. This is an alias for NewNTCP2Listener for consistency with the transport API.

func (*Listener) Accept added in v0.1.6

func (nl *Listener) Accept() (net.Conn, error)

Accept waits for and returns the next connection to the listener. The returned connection is wrapped in an NTCP2Conn configured as a responder with the full NTCP2 cipher suite, protocol name, and modifiers.

The returned connection has NOT yet performed the Noise handshake. RemoteAddr().(*Addr).RouterHash is a zero value until Handshake(ctx) and PropagatePeerStaticKey() complete. Most callers should use AcceptWithHandshake (if available) or call Handshake explicitly after Accept.

func (*Listener) AcceptWithHandshake added in v0.1.6

func (nl *Listener) AcceptWithHandshake(ctx context.Context) (ConnIface, error)

AcceptWithHandshake waits for the next connection and automatically performs the NTCP2 handshake. This mirrors DialNTCP2WithHandshakeContext for the responder side.

func (*Listener) Addr added in v0.1.6

func (nl *Listener) Addr() net.Addr

Addr returns the listener's network address. This is an NTCP2Addr that wraps the underlying listener's address.

func (*Listener) Close added in v0.1.6

func (nl *Listener) Close() error

Close closes the listener and prevents new connections from being accepted. Any blocked Accept operations will be unblocked and return errors.

type NTCP2PaddingModifier

type NTCP2PaddingModifier struct {
	// contains filtered or unexported fields
}

NTCP2PaddingModifier implements production-grade NTCP2-specific padding strategies. Supports I2P NTCP2 specification requirements including: - Cleartext padding for messages 1 and 2 (outside AEAD frames) - AEAD padding for message 3 and data phase (inside AEAD frames with type 254) - Cryptographically secure random padding distribution - Configurable padding ratios for traffic analysis resistance

Padding computation and I2P block wire format operations are provided by the shared handshake.PaddingEngine. NTCP2-specific concerns (AEAD removal with trailing-block scan, frame validation) remain in this type.

All exported methods are safe for concurrent use.

func NewNTCP2PaddingModifier

func NewNTCP2PaddingModifier(name string, minPadding, maxPadding int, useAEADPadding bool) (*NTCP2PaddingModifier, error)

NewNTCP2PaddingModifier creates a new production-grade NTCP2 padding modifier.

Parameters:

  • name: identifier for logging and debugging
  • minPadding: minimum padding bytes (0-65516)
  • maxPadding: maximum padding bytes (>= minPadding, 0-65516)
  • useAEADPadding: false for messages 1-2 (cleartext), true for message 3+ (AEAD)

The modifier uses cryptographically secure random padding by default. Padding sizes follow I2P NTCP2 specification guidelines.

func NewNTCP2PaddingModifierForTesting

func NewNTCP2PaddingModifierForTesting(name string, minPadding, maxPadding int, useAEADPadding bool) (*NTCP2PaddingModifier, error)

NewNTCP2PaddingModifierForTesting creates a modifier with deterministic padding for testing. This should NEVER be used in production as it compromises security.

func NewNTCP2PaddingModifierWithRatio

func NewNTCP2PaddingModifierWithRatio(name string, minPadding, maxPadding int, useAEADPadding bool, paddingRatio float64) (*NTCP2PaddingModifier, error)

NewNTCP2PaddingModifierWithRatio creates a new NTCP2 padding modifier with a specific padding ratio.

Parameters:

  • name: identifier for logging and debugging
  • minPadding: minimum padding bytes (0-65516)
  • maxPadding: maximum padding bytes (>= minPadding, 0-65516)
  • useAEADPadding: false for messages 1-2 (cleartext), true for message 3+ (AEAD)
  • paddingRatio: ratio of padding to data (0.0 to 15.9375 as per I2P NTCP2 spec)

A paddingRatio of 0.0 means no ratio-based padding (uses min/max only). A paddingRatio of 1.0 means 100% padding (double the message size).

func (*NTCP2PaddingModifier) Clone added in v0.1.6

Clone creates a deep copy of the NTCP2PaddingModifier with independent state. This implements handshake.ModifierCloner to support safe Config.Clone() operations.

func (*NTCP2PaddingModifier) Close added in v0.1.6

func (npm *NTCP2PaddingModifier) Close() error

Close is a no-op for NTCP2PaddingModifier because it holds no sensitive key material. It satisfies the HandshakeModifier lifecycle contract.

func (*NTCP2PaddingModifier) EstimatePaddingSize

func (npm *NTCP2PaddingModifier) EstimatePaddingSize(dataLen int) int

EstimatePaddingSize estimates the padding size for a given data length. Useful for pre-allocating buffers and bandwidth calculations.

func (*NTCP2PaddingModifier) GetPaddingLimits

func (npm *NTCP2PaddingModifier) GetPaddingLimits() (int, int)

GetPaddingLimits returns the current min/max padding limits.

func (*NTCP2PaddingModifier) GetPaddingRatio

func (npm *NTCP2PaddingModifier) GetPaddingRatio() float64

GetPaddingRatio returns the current padding ratio.

func (*NTCP2PaddingModifier) IsAEADMode

func (npm *NTCP2PaddingModifier) IsAEADMode() bool

IsAEADMode returns true if this modifier is configured for AEAD padding (message 3+).

func (*NTCP2PaddingModifier) ModifyInbound

func (npm *NTCP2PaddingModifier) ModifyInbound(phase handshake.HandshakePhase, data []byte) ([]byte, error)

ModifyInbound removes NTCP2-specific padding.

PhaseFinal (message 3) is skipped — padding in message 3 is inside the encrypted payload and parsed by the block-format layer, not the modifier.

func (*NTCP2PaddingModifier) ModifyOutbound

func (npm *NTCP2PaddingModifier) ModifyOutbound(phase handshake.HandshakePhase, data []byte) ([]byte, error)

ModifyOutbound adds NTCP2-specific padding based on message phase.

PhaseFinal (message 3) is explicitly skipped because the handshake code manages message 3 padding at the plaintext level — it must be included in m3p2Len which is committed in message 1 before the modifier runs. AEAD padding is only applied during PhaseData (post-handshake frames).

func (*NTCP2PaddingModifier) Name

func (npm *NTCP2PaddingModifier) Name() string

Name returns the modifier name for logging and debugging.

func (*NTCP2PaddingModifier) SetPaddingLimits

func (npm *NTCP2PaddingModifier) SetPaddingLimits(minPadding, maxPadding int) error

SetPaddingLimits updates the padding limits for dynamic adjustment. Supports I2P NTCP2 options negotiation during data phase.

func (*NTCP2PaddingModifier) SetPaddingRatio

func (npm *NTCP2PaddingModifier) SetPaddingRatio(ratio float64) error

SetPaddingRatio updates the padding ratio for dynamic adjustment during connection. This supports I2P NTCP2 options negotiation where padding parameters can be updated.

func (*NTCP2PaddingModifier) ValidateAEADFrame

func (npm *NTCP2PaddingModifier) ValidateAEADFrame(data []byte) bool

ValidateAEADFrame validates that a frame contains properly formatted AEAD blocks. Returns true if the frame structure is valid according to I2P NTCP2 spec.

type ReplayCache added in v0.1.6

type ReplayCache struct {
	// contains filtered or unexported fields
}

ReplayCache is a thread-safe, bounded, TTL-based cache for detecting replayed NTCP2 handshake ephemeral keys. It is shared across all listener goroutines within a single router instance.

The cache stores the first 32 bytes of each message 1 (the ephemeral key X) and rejects duplicates within the TTL window.

ReplayCache implements the ReplayDetector interface.

func NewReplayCache added in v0.1.6

func NewReplayCache() *ReplayCache

NewReplayCache creates a new replay cache and starts a background cleanup goroutine. Call Close() when the cache is no longer needed.

func (*ReplayCache) CheckAndAdd added in v0.1.6

func (rc *ReplayCache) CheckAndAdd(ephemeralKey [32]byte) bool

CheckAndAdd checks whether an ephemeral key has been seen before. If the key is new, it is added to the cache and false is returned (not a replay). If the key has been seen within the TTL window, true is returned (replay detected).

This is the primary method called by the listener before processing message 1.

func (*ReplayCache) Close added in v0.1.6

func (rc *ReplayCache) Close()

Close stops the background cleanup goroutine and releases resources. Close is idempotent — calling it more than once is safe and will not panic.

func (*ReplayCache) Size added in v0.1.6

func (rc *ReplayCache) Size() int

Size returns the current number of entries in the cache.

type ReplayDetector added in v0.1.6

type ReplayDetector interface {
	// CheckAndAdd returns true if the ephemeral key has been seen before (replay).
	// If the key is new, it is added to the cache and false is returned.
	CheckAndAdd(ephemeralKey [32]byte) bool

	// Size returns the current number of entries in the replay cache.
	Size() int

	// Close releases resources (stops cleanup goroutine if any).
	Close()
}

ReplayDetector checks for replayed ephemeral keys during NTCP2 handshakes. Implementations maintain a TTL-based cache of recently seen ephemeral keys to prevent handshake replay attacks.

The cache should automatically evict entries older than the configured TTL (typically derived from ClockSkewTolerance). Implementations must be safe for concurrent use.

type SipHashLengthModifier

type SipHashLengthModifier = pkgsiphash.LengthModifier

SipHashLengthModifier implements NTCP2's SipHash-2-4 length obfuscation for data phase frame lengths. The canonical implementation lives in handshake/siphash; this alias makes the type directly accessible from the ntcp2 package without an extra import.

func NewSipHashLengthModifier

func NewSipHashLengthModifier(name string, sipKeys [2]uint64, initialIV uint64) *SipHashLengthModifier

NewSipHashLengthModifier creates a new SipHash length obfuscation modifier with shared keys for both directions.

func NewSipHashLengthModifierDirectional added in v0.1.6

func NewSipHashLengthModifierDirectional(name string, outKeys, inKeys [2]uint64, outIV, inIV uint64) *SipHashLengthModifier

NewSipHashLengthModifierDirectional creates a SipHash length obfuscation modifier with per-direction keys as required by the NTCP2 spec.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL