Add challenge generator
This commit is contained in:
parent
0fbdfe1b83
commit
750223b2cc
97
internal/pow/generator.go
Normal file
97
internal/pow/generator.go
Normal 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)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue