From 2a4060bc8afa2b1e007918c727ea9224f5666490 Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Fri, 22 Aug 2025 16:40:03 +0700 Subject: [PATCH] Add PoW challenge structure and types --- docs/IMPLEMENTATION.md | 4 +- internal/pow/types.go | 134 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 internal/pow/types.go diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index b614357..a522ed7 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -4,8 +4,8 @@ **Goal**: Create standalone, testable PoW package with HMAC-signed stateless challenges - [ ] **Project Setup** - - [ ] Initialize Go module and basic project structure - - [ ] Create PoW challenge structure and types + - [X] Initialize Go module and basic project structure + - [X] Create PoW challenge structure and types - [ ] Set up testing framework and utilities - [ ] **Challenge Generation & HMAC Security** diff --git a/internal/pow/types.go b/internal/pow/types.go new file mode 100644 index 0000000..71f49d5 --- /dev/null +++ b/internal/pow/types.go @@ -0,0 +1,134 @@ +package pow + +import ( + "crypto/hmac" + "crypto/sha256" + "errors" + "fmt" + "time" +) + +// PoW package specific errors +var ( + ErrInvalidHMAC = errors.New("challenge HMAC signature is invalid") + ErrExpiredChallenge = errors.New("challenge has expired") + ErrInvalidSolution = errors.New("proof of work solution is invalid") + ErrInvalidDifficulty = errors.New("difficulty level is invalid") + ErrMalformedChallenge = errors.New("challenge format is malformed") + ErrInvalidConfig = errors.New("configuration is invalid") +) + +// Challenge represents a Proof of Work challenge with HMAC authentication +type Challenge struct { + Timestamp int64 `json:"timestamp"` + Difficulty int `json:"difficulty"` + Resource string `json:"resource"` + Random []byte `json:"random"` + HMAC []byte `json:"hmac"` +} + +// CanonicalBytes returns the canonical byte representation for HMAC signing +func (c *Challenge) CanonicalBytes() []byte { + // Use consistent field ordering: timestamp:difficulty:resource:random + buf := make([]byte, 0, 64) + buf = fmt.Appendf(buf, "%d:%d:%s:%x", + c.Timestamp, + c.Difficulty, + c.Resource, + c.Random, + ) + return buf +} + +// BuildSolutionString creates the string used for PoW computation +func (c *Challenge) BuildSolutionString(nonce uint64) []byte { + // Format: resource:timestamp:difficulty:random:nonce + buf := make([]byte, 0, 64) + buf = fmt.Appendf(buf, "%s:%d:%d:%x:%d", + c.Resource, + c.Timestamp, + c.Difficulty, + c.Random, + nonce, + ) + return buf +} + +// VerifySolution verifies if a solution is valid for this challenge +func (c *Challenge) VerifySolution(nonce uint64) bool { + // Build solution string + solutionStr := c.BuildSolutionString(nonce) + + // Compute SHA-256 hash + hash := sha256.Sum256(solutionStr) + + // Check if hash has required leading zero bits + return hasLeadingZeroBits(hash[:], c.Difficulty) +} + +// hasLeadingZeroBits checks if hash has the required number of leading zero bits +func hasLeadingZeroBits(hash []byte, difficulty int) bool { + full := difficulty >> 3 // number of whole zero bytes + rem := uint(difficulty & 7) // remaining leading zero bits + + for i := range full { + if hash[i] != 0 { + return false + } + } + if rem == 0 { + return true + } + mask := byte(0xFF) << (8 - rem) // e.g., rem=3 => 11100000 + return (hash[full] & mask) == 0 +} + +// IsExpired checks if challenge has exceeded TTL +func (c *Challenge) IsExpired(ttl time.Duration) bool { + challengeTime := time.Unix(c.Timestamp, 0) + return time.Since(challengeTime) > ttl +} + +// Sign creates and sets HMAC signature for the challenge +func (c *Challenge) Sign(secret []byte) { + // Create canonical bytes for signing (excluding HMAC field) + canonical := c.CanonicalBytes() + + // Create HMAC + h := hmac.New(sha256.New, secret) + h.Write(canonical) + c.HMAC = h.Sum(nil) +} + +// VerifyHMAC verifies the HMAC signature of the challenge +func (c *Challenge) VerifyHMAC(secret []byte) error { + if len(c.HMAC) == 0 { + return ErrMalformedChallenge + } + + // Create canonical bytes (excluding HMAC) + canonical := c.CanonicalBytes() + + // Compute expected HMAC + h := hmac.New(sha256.New, secret) + h.Write(canonical) + expectedSignature := h.Sum(nil) + + // Compare signatures + if !hmac.Equal(c.HMAC, expectedSignature) { + return ErrInvalidHMAC + } + + return nil +} + +// Solution represents a client's solution to a PoW challenge +type Solution struct { + Challenge Challenge `json:"challenge"` + Nonce uint64 `json:"nonce"` +} + +// Verify verifies if this solution is valid +func (s *Solution) Verify() bool { + return s.Challenge.VerifySolution(s.Nonce) +}