mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
package cap
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCapFullFlow(t *testing.T) {
|
|
secret := []byte("a-very-long-secret-key-at-least-16-bytes")
|
|
store := NewMemoryStore(1 * time.Minute)
|
|
|
|
manager := NewManager(Config{
|
|
Secret: secret,
|
|
ChallengeCount: 3, // small count for fast test
|
|
ChallengeSize: 32,
|
|
ChallengeDifficulty: 3, // small difficulty for fast test
|
|
ChallengeTTL: 5 * time.Second,
|
|
TokenTTL: 10 * time.Second,
|
|
}, store)
|
|
|
|
scope := "test-scope"
|
|
resp, err := manager.Generate(scope)
|
|
if err != nil {
|
|
t.Fatalf("Generate failed: %v", err)
|
|
}
|
|
|
|
if resp.Challenge.C != 3 {
|
|
t.Errorf("Expected count 3, got %d", resp.Challenge.C)
|
|
}
|
|
|
|
// Solve the challenge (acting as client)
|
|
solutions := make([]int, resp.Challenge.C)
|
|
tokenFnv := fnv1a(resp.Token)
|
|
for i := 0; i < resp.Challenge.C; i++ {
|
|
idxStr := strconv.Itoa(i + 1)
|
|
saltSeed := fnv1aResume(tokenFnv, idxStr)
|
|
targetSeed := fnv1aResume(saltSeed, "d")
|
|
salt := prngFromHash(saltSeed, resp.Challenge.S)
|
|
target := prngFromHash(targetSeed, resp.Challenge.D)
|
|
|
|
// Brute force the PoW solution
|
|
var found bool
|
|
for nonce := 0; nonce < 1000000; nonce++ {
|
|
hashInput := salt + strconv.Itoa(nonce)
|
|
hashBytes := sha256.Sum256([]byte(hashInput))
|
|
hashHex := hex.EncodeToString(hashBytes[:])
|
|
if strings.HasPrefix(hashHex, target) {
|
|
solutions[i] = nonce
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("Failed to solve puzzle %d", i)
|
|
}
|
|
}
|
|
|
|
// Redeem
|
|
ctx := context.Background()
|
|
redeemResp, err := manager.Redeem(ctx, resp.Token, solutions, scope)
|
|
if err != nil {
|
|
t.Fatalf("Redeem failed: %v", err)
|
|
}
|
|
if !redeemResp.Success {
|
|
t.Fatalf("Redeem returned success=false: %s", redeemResp.Error)
|
|
}
|
|
if redeemResp.Token == "" {
|
|
t.Fatalf("Expected token, got empty")
|
|
}
|
|
|
|
// Verify the token
|
|
valid, err := manager.VerifyToken(ctx, redeemResp.Token, scope)
|
|
if err != nil {
|
|
t.Fatalf("VerifyToken failed: %v", err)
|
|
}
|
|
if !valid {
|
|
t.Fatalf("Expected redeem token to be valid")
|
|
}
|
|
|
|
// Verify token is one-time use
|
|
validAgain, err := manager.VerifyToken(ctx, redeemResp.Token, scope)
|
|
if err != nil {
|
|
t.Fatalf("VerifyToken second call failed: %v", err)
|
|
}
|
|
if validAgain {
|
|
t.Fatalf("Expected redeem token to be single-use (invalidated after verification)")
|
|
}
|
|
}
|