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 ¶
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 ¶
DecodeTyped calls Codec.Decode and unmarshals the payload into T via JSON.
func EncodeTyped ¶
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 ¶
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 ¶
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.