148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
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
|
|
}
|