package challenge import ( "crypto/rand" "fmt" "time" ) // Config holds configuration for the PoW system type Config struct { DefaultDifficulty int // Default difficulty in bits (e.g., 4) MaxDifficulty int // Maximum allowed difficulty (e.g., 10) MinDifficulty int // Minimum allowed difficulty (e.g., 3) ChallengeTTL time.Duration // Time-to-live for challenges (e.g., 5 minutes) HMACSecret []byte // Secret key for HMAC signing Resource string // Resource identifier (e.g., "quotes") RandomBytes int // Number of random bytes in challenge (e.g., 6) LoadAdjustmentBits int // Extra difficulty bits when server under load FailurePenaltyBits int // Extra difficulty bits per failure group MaxFailurePenaltyBits int // Maximum failure penalty bits } // configOption represents a functional option for Config type configOption func(*Config) // WithDefaultDifficulty sets the default difficulty func WithDefaultDifficulty(difficulty int) configOption { return func(c *Config) { c.DefaultDifficulty = difficulty } } // WithMaxDifficulty sets the maximum difficulty func WithMaxDifficulty(difficulty int) configOption { return func(c *Config) { c.MaxDifficulty = difficulty } } // WithMinDifficulty sets the minimum difficulty func WithMinDifficulty(difficulty int) configOption { return func(c *Config) { c.MinDifficulty = difficulty } } // WithChallengeTTL sets the challenge time-to-live func WithChallengeTTL(ttl time.Duration) configOption { return func(c *Config) { c.ChallengeTTL = ttl } } // WithHMACSecret sets the HMAC secret func WithHMACSecret(secret []byte) configOption { return func(c *Config) { c.HMACSecret = secret } } // WithResource sets the resource identifier func WithResource(resource string) configOption { return func(c *Config) { c.Resource = resource } } // WithRandomBytes sets the number of random bytes func WithRandomBytes(bytes int) configOption { return func(c *Config) { c.RandomBytes = bytes } } // WithLoadAdjustmentBits sets the load adjustment bits func WithLoadAdjustmentBits(bits int) configOption { return func(c *Config) { c.LoadAdjustmentBits = bits } } // WithFailurePenaltyBits sets the failure penalty bits func WithFailurePenaltyBits(bits int) configOption { return func(c *Config) { c.FailurePenaltyBits = bits } } // WithMaxFailurePenaltyBits sets the maximum failure penalty bits func WithMaxFailurePenaltyBits(bits int) configOption { return func(c *Config) { c.MaxFailurePenaltyBits = bits } } // NewConfig creates and validates a new PoW configuration with defaults func NewConfig(opts ...configOption) (*Config, error) { // Generate default HMAC secret secret := make([]byte, 32) rand.Read(secret) // Create config with defaults config := &Config{ DefaultDifficulty: 4, MaxDifficulty: 10, MinDifficulty: 3, ChallengeTTL: 5 * time.Minute, HMACSecret: secret, Resource: "quotes", RandomBytes: 6, LoadAdjustmentBits: 1, FailurePenaltyBits: 2, MaxFailurePenaltyBits: 6, } // Apply options for _, opt := range opts { opt(config) } // Validate configuration if config.RandomBytes <= 0 || config.RandomBytes > 32 { return nil, fmt.Errorf("%w: random bytes must be between 1-32, got %d", ErrInvalidConfig, config.RandomBytes) } if config.MinDifficulty < 1 { return nil, fmt.Errorf("%w: minimum difficulty must be >= 1, got %d", ErrInvalidConfig, config.MinDifficulty) } if config.MaxDifficulty < config.MinDifficulty { return nil, fmt.Errorf("%w: maximum difficulty (%d) must be >= minimum difficulty (%d)", ErrInvalidConfig, config.MaxDifficulty, config.MinDifficulty) } if config.DefaultDifficulty < config.MinDifficulty || config.DefaultDifficulty > config.MaxDifficulty { return nil, fmt.Errorf("%w: default difficulty (%d) must be between min (%d) and max (%d)", ErrInvalidConfig, config.DefaultDifficulty, config.MinDifficulty, config.MaxDifficulty) } if len(config.HMACSecret) == 0 { return nil, fmt.Errorf("%w: HMAC secret cannot be empty", ErrInvalidConfig) } if config.ChallengeTTL <= 0 { return nil, fmt.Errorf("%w: challenge TTL must be positive, got %v", ErrInvalidConfig, config.ChallengeTTL) } return config, nil }