Add challenge generator

This commit is contained in:
Savely Krendelhoff 2025-08-22 17:44:31 +07:00
parent 0fbdfe1b83
commit 750223b2cc
No known key found for this signature in database
GPG key ID: F70DFD34F40238DE

97
internal/pow/generator.go Normal file
View file

@ -0,0 +1,97 @@
package pow
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"time"
)
// Generator handles creation of PoW challenges with HMAC signing
type Generator struct {
config *Config
}
// GenerateOption represents a functional option for challenge generation
type generateOption func(*Challenge)
// WithDifficulty sets custom difficulty for the challenge
func WithDifficulty(difficulty int) generateOption {
return func(c *Challenge) {
c.Difficulty = difficulty
}
}
// NewGenerator creates a new challenge generator
func NewGenerator(config *Config) *Generator {
return &Generator{
config: config,
}
}
// GenerateChallenge creates a new HMAC-signed PoW challenge
func (g *Generator) GenerateChallenge(opts ...generateOption) (*Challenge, error) {
// Create challenge with defaults (no random data yet)
challenge := &Challenge{
Timestamp: time.Now().Unix(),
Difficulty: g.config.DefaultDifficulty,
Resource: g.config.Resource,
}
// Apply options
for _, opt := range opts {
opt(challenge)
}
// Validate difficulty bounds
if challenge.Difficulty < g.config.MinDifficulty || challenge.Difficulty > g.config.MaxDifficulty {
return nil, ErrInvalidDifficulty
}
// Generate cryptographic random data only after validation
challenge.Random = g.generateRandom(12) // 12 hex chars = 6 bytes
// Sign the challenge with HMAC
signature, err := g.signChallenge(challenge)
if err != nil {
return nil, fmt.Errorf("failed to sign challenge: %w", err)
}
challenge.HMAC = signature
return challenge, nil
}
// signChallenge creates HMAC signature for the challenge
func (g *Generator) signChallenge(challenge *Challenge) (string, error) {
// Create canonical string for signing (excluding HMAC field)
canonical := g.canonicalChallengeString(challenge)
// Create HMAC
h := hmac.New(sha256.New, g.config.HMACSecret)
h.Write([]byte(canonical))
signature := h.Sum(nil)
// Return base64url encoded signature
return base64.RawURLEncoding.EncodeToString(signature), nil
}
// canonicalChallengeString creates a consistent string representation for HMAC
func (g *Generator) canonicalChallengeString(challenge *Challenge) string {
// Use consistent field ordering: timestamp:difficulty:resource:random
return fmt.Sprintf("%d:%d:%s:%s",
challenge.Timestamp,
challenge.Difficulty,
challenge.Resource,
challenge.Random,
)
}
// generateRandom creates cryptographic random hex string
func (g *Generator) generateRandom(length int) string {
bytes := make([]byte, length/2)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}