package admin import ( "crypto/hmac" "crypto/sha256" "encoding/binary" "encoding/hex" "net/http" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" ) type verifyDrawRequest struct { AlgoVersion string `json:"algo_version" binding:"required"` IssueID int64 `json:"issue_id" binding:"required"` DrawID int64 `json:"draw_id" binding:"required"` ClientSeed string `json:"client_seed" binding:"required"` ServerSeedHash string `json:"server_seed_hash" binding:"required"` ItemsRoot string `json:"items_root" binding:"required"` WeightsTotal uint64 `json:"weights_total" binding:"required"` SelectedIndex int `json:"selected_index" binding:"required,min=0"` RandProof string `json:"rand_proof" binding:"required"` } type verifyDrawResponse struct { Valid bool `json:"valid"` Message string `json:"message"` ServerSeedHashExpected string `json:"server_seed_hash_expected,omitempty"` ItemsRootExpected string `json:"items_root_expected,omitempty"` } func (h *handler) VerifyDrawReceipt() core.HandlerFunc { return func(ctx core.Context) { var req verifyDrawRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } cm, err := h.activity.GetIssueRandomCommit(ctx.RequestContext(), req.IssueID) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, err.Error())) return } if cm == nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, "未找到承诺")) return } serverSeedHashBytes := cm.ServerSeedHash[:] itemsRootBytes := cm.ItemsRoot[:] if hex.EncodeToString(serverSeedHashBytes) != req.ServerSeedHash { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "server_seed_hash 不匹配", ServerSeedHashExpected: hex.EncodeToString(serverSeedHashBytes), }) return } if hex.EncodeToString(itemsRootBytes) != req.ItemsRoot { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "items_root 不匹配", ItemsRootExpected: hex.EncodeToString(itemsRootBytes), }) return } rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), req.IssueID) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListIssueRewardsError, err.Error())) return } if req.SelectedIndex >= len(rewards) { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "selected_index 超出奖励范围", }) return } sel := rewards[req.SelectedIndex] if sel.Weight <= 0 || (sel.Quantity != -1 && sel.Quantity <= 0) { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "selected_index 对应奖励不可抽", }) return } proofBytes, err := hex.DecodeString(req.RandProof) if err != nil { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "rand_proof 格式错误", }) return } clientSeedBytes, err := hex.DecodeString(req.ClientSeed) if err != nil { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "client_seed 格式错误", }) return } master := unmaskSeed(cm.ServerSeedMaster, cm.IssueID, cm.StateVersion) subInput := make([]byte, 16) binary.BigEndian.PutUint64(subInput[:8], uint64(req.IssueID)) binary.BigEndian.PutUint64(subInput[8:], uint64(req.DrawID)) mac := hmac.New(sha256.New, master) mac.Write(subInput) serverSubSeed := mac.Sum(nil) enc := encodeMessage(req.AlgoVersion, req.IssueID, req.DrawID, 0, clientSeedBytes, 1, itemsRootBytes, req.WeightsTotal) entropy := hmacSha256(serverSubSeed, enc) var final []byte { W := req.WeightsTotal var counter uint64 for { R := binary.BigEndian.Uint64(entropy[:8]) M := (uint64(^uint64(0)) / W) * W if R < M { final = entropy break } counter++ cenc := make([]byte, len(enc)+8) copy(cenc, enc) binary.BigEndian.PutUint64(cenc[len(enc):], counter) entropy = hmacSha256(serverSubSeed, cenc) } } if !hmac.Equal(final, proofBytes) { ctx.Payload(&verifyDrawResponse{ Valid: false, Message: "rand_proof 验证失败", }) return } ctx.Payload(&verifyDrawResponse{Valid: true, Message: "验证通过"}) } }