callback

package
v0.97.12 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package callback provides HMAC-SHA256 signed Telegram CallbackData encoding.

Security model

Telegram inline keyboard callbacks carry a free-form string up to 64 bytes. Without signing, an attacker can enumerate valid payload values (e.g. iterate partner-edge IDs) by replaying or forging callback_data. HMAC signing makes forgery computationally infeasible.

Wire format

<prefix>:<base64url-no-pad(payload)>:<base64url-no-pad(hmac[:8])>

The HMAC-SHA256 input is the pre-signature portion of the wire string:

mac_input = prefix + ":" + base64url-no-pad(payload)

Binding the prefix into the MAC prevents prefix-swapping attacks (e.g. turning a "confirm" callback into a "delete" callback with the same payload).

HMAC truncation

Only the first 8 bytes (64 bits) of HMAC-SHA256 are included. This fits the 64-byte Telegram limit while providing forgery cost ≈ 2^64 operations under a uniform key. Per NIST SP 800-107 §5.2, truncation to ≥ 32 bits is acceptable for MAC use; 64 bits is well above that threshold.

8 bytes of HMAC encodes to exactly 11 base64url-no-pad characters. Fixed overhead per encoded string: len(prefix) + 1 + 11 + 1 = len(prefix) + 13. With an empty prefix the usable payload budget is ≈ 38 raw bytes (51 after b64).

Signature verification

Codec.Decode uses crypto/subtle.ConstantTimeCompare to prevent timing side-channels on the 8-byte MAC comparison.

Usage

// Derive the secret from the bot token + env salt — never use the raw token.
secret := deriveSecret(botToken, os.Getenv("CALLBACK_SALT"))
codec := callback.New(secret)

// Encoding a struct payload:
data, err := callback.EncodeTyped(codec, "partner", PartnerAction{ID: 42, Op: "approve"})

// Decoding in the update handler:
prefix, action, err := callback.DecodeTyped[PartnerAction](codec, update.CallbackQuery.Data)
if err != nil {
    // ErrInvalidSignature → reject silently (bot-enumeration attempt)
    // ErrMalformed       → reject (garbage input)
    // ErrTooLarge        → shouldn't occur at decode; your Encode is misconfigured
    return
}

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidSignature is returned when the HMAC does not match.
	// Uses crypto/subtle.ConstantTimeCompare to prevent timing side-channels.
	ErrInvalidSignature = errors.New("callback: invalid signature")

	// ErrTooLarge is returned when the encoded string would exceed the
	// Telegram 64-byte CallbackData limit.
	ErrTooLarge = errors.New("callback: encoded data exceeds 64 bytes")

	// ErrMalformed is returned when the input does not match the expected
	// wire format (prefix:b64payload:b64sig).
	ErrMalformed = errors.New("callback: malformed encoding")
)

Sentinel errors returned by Codec methods.

Functions

func DecodeTyped

func DecodeTyped[T any](c *Codec, callbackData string) (prefix string, v T, err error)

DecodeTyped calls Codec.Decode and unmarshals the payload into T via JSON.

func EncodeTyped

func EncodeTyped[T any](c *Codec, prefix string, v T) (string, error)

EncodeTyped marshals v to JSON and calls Codec.Encode. Useful for struct payloads; relies on json.Marshal for serialization.

Types

type Codec

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

Codec signs and verifies Telegram CallbackData using HMAC-SHA256. The zero value is not usable; create via New.

func New

func New(secret []byte) *Codec

New creates a Codec using the given secret. The secret should be derived from the bot token + an application-specific salt; it must not be empty in production.

func (*Codec) Decode

func (c *Codec) Decode(callbackData string) (prefix string, payload []byte, err error)

Decode verifies the signature and returns the prefix and original payload.

Returns:

  • ErrInvalidSignature if the HMAC does not match (uses constant-time compare).
  • ErrMalformed if the input does not have exactly three colon-separated parts.

func (*Codec) Encode

func (c *Codec) Encode(prefix string, payload []byte) (string, error)

Encode produces a signed CallbackData string in the format:

<prefix>:<base64url(payload)>:<base64url(hmac[:8])>

Returns ErrTooLarge if the result exceeds 64 bytes (Telegram limit). Returns an error if prefix contains ':', which would make decoding ambiguous.

Jump to

Keyboard shortcuts

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