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) }