Skip to content

[Security] Transaction hash replay attack possible - same payment used multiple times to access AI service #241

Description

@anshul23102

Problem

If the system verifies that a transaction hash is valid on-chain but does not record it as used, the same transaction hash can be replayed indefinitely to access the AI service for free after paying once:

func verifyPayment(txHash string) bool {
    // Only checks if tx exists and has correct amount on-chain.
    // Does NOT check if this tx was already used.
    return checkBlockchain(txHash)
}

An attacker pays once, receives a valid transaction hash, and replays it thousands of times.

Proposed Fix

Store used transaction hashes in a Redis set (fast, with TTL matching the on-chain finality window) or a database:

import "github.com/redis/go-redis/v9"

var rdb = redis.NewClient(&redis.Options{Addr: "localhost:6379"})

func isTransactionUsed(ctx context.Context, txHash string) (bool, error) {
    return rdb.SIsMember(ctx, "used_tx_hashes", txHash).Result()
}

func markTransactionUsed(ctx context.Context, txHash string) error {
    // Keep for 30 days (transaction replay window)
    pipe := rdb.Pipeline()
    pipe.SAdd(ctx, "used_tx_hashes", txHash)
    pipe.Expire(ctx, "used_tx_hashes", 30*24*time.Hour)
    _, err := pipe.Exec(ctx)
    return err
}

func handlePayment(w http.ResponseWriter, r *http.Request) {
    // ...after verifying amount...
    used, err := isTransactionUsed(ctx, req.TxHash)
    if err != nil || used {
        http.Error(w, "Transaction already used", http.StatusPaymentRequired)
        return
    }
    if err := markTransactionUsed(ctx, req.TxHash); err != nil {
        http.Error(w, "Internal error", http.StatusInternalServerError)
        return
    }
    serveAIResponse(w, req.Prompt)
}

Acceptance Criteria

  • Each transaction hash recorded as used immediately after first successful verification
  • Replay of a used transaction hash returns 402 with "Transaction already used" message
  • Used hash store is atomic (race condition between two concurrent requests with the same hash is prevented)
  • TTL on used hashes covers the on-chain finality + dispute window (minimum 30 days)
  • Load test confirms no replay succeeds even under concurrent submission

Metadata

Metadata

Assignees

Labels

TypeScriptTypeScript codebugSomething isn't workinggoPull requests that update go codegssoc:approvedApproved for GSSoC contributionlevel:advancedLarge, risky, or cross-service work requiring strong project context.triageNeeds maintainer triage.type:bugA defect or regression in existing behavior.type:securitySecurity, abuse resistance, secret safety, or payment integrity work.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions