Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
新增随机种子生成与验证逻辑,包括: 1. 添加随机承诺生成接口 2. 实现抽奖执行与验证流程 3. 新增批量用户创建与删除功能 4. 添加抽奖收据记录表 5. 完善配置管理与错误码 新增测试用例验证随机算法正确性
140 lines
5.2 KiB
Go
140 lines
5.2 KiB
Go
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: "验证通过"})
|
|
}
|
|
} |