Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
新增随机种子生成与验证逻辑,包括: 1. 添加随机承诺生成接口 2. 实现抽奖执行与验证流程 3. 新增批量用户创建与删除功能 4. 添加抽奖收据记录表 5. 完善配置管理与错误码 新增测试用例验证随机算法正确性
145 lines
4.3 KiB
Go
145 lines
4.3 KiB
Go
package activity
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"time"
|
|
)
|
|
|
|
func (s *service) ExecuteDraw(ctx context.Context, issueID int64) (*Receipt, error) {
|
|
cm, err := s.GetIssueRandomCommit(ctx, issueID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cm == nil {
|
|
return nil, nil
|
|
}
|
|
master := unmaskSeed(cm.ServerSeedMaster, cm.IssueID, cm.StateVersion)
|
|
items, err := s.readDB.ActivityRewardSettings.WithContext(ctx).
|
|
Where(s.readDB.ActivityRewardSettings.IssueID.Eq(issueID)).
|
|
Order(s.readDB.ActivityRewardSettings.Sort).
|
|
Find()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var snapshot []ReceiptItem
|
|
var total int64
|
|
for _, it := range items {
|
|
snapshot = append(snapshot, ReceiptItem{ID: it.ID, Name: it.Name, Weight: it.Weight, QuantityBefore: it.Quantity})
|
|
if it.Weight > 0 && (it.Quantity == -1 || it.Quantity > 0) {
|
|
total += int64(it.Weight)
|
|
}
|
|
}
|
|
if total <= 0 {
|
|
return nil, nil
|
|
}
|
|
drawId := time.Now().UnixNano()
|
|
clientSeed := make([]byte, 32)
|
|
_, _ = rand.Read(clientSeed)
|
|
nonce := uint64(1)
|
|
subInput := make([]byte, 16)
|
|
binary.BigEndian.PutUint64(subInput[:8], uint64(issueID))
|
|
binary.BigEndian.PutUint64(subInput[8:16], uint64(drawId))
|
|
mac := hmac.New(sha256.New, master)
|
|
mac.Write(subInput)
|
|
serverSubSeed := mac.Sum(nil)
|
|
enc := encodeMessage(cm.AlgoVersion, issueID, drawId, 0, clientSeed, nonce, cm.ItemsRoot[:], uint64(total))
|
|
entropy := hmacSha256(serverSubSeed, enc)
|
|
pos, proof := rejectSample(entropy, serverSubSeed, enc, uint64(total))
|
|
var acc uint64
|
|
var selIndex int
|
|
var selID int64
|
|
for i, it := range items {
|
|
if it.Weight <= 0 || !(it.Quantity == -1 || it.Quantity > 0) {
|
|
continue
|
|
}
|
|
w := uint64(it.Weight)
|
|
if pos < acc+w {
|
|
selIndex = i
|
|
selID = it.ID
|
|
break
|
|
}
|
|
acc += w
|
|
}
|
|
rec := &Receipt{
|
|
AlgoVersion: cm.AlgoVersion,
|
|
RoundId: issueID,
|
|
DrawId: drawId,
|
|
ClientId: 0,
|
|
Timestamp: time.Now().UnixMilli(),
|
|
ServerSeedHash: cm.ServerSeedHash[:],
|
|
ServerSubSeed: serverSubSeed,
|
|
ClientSeed: clientSeed,
|
|
Nonce: nonce,
|
|
Items: snapshot,
|
|
ItemsRoot: cm.ItemsRoot[:],
|
|
WeightsTotal: uint64(total),
|
|
SelectedIndex: selIndex,
|
|
SelectedItemId: selID,
|
|
RandProof: proof,
|
|
Signature: nil,
|
|
}
|
|
return rec, nil
|
|
}
|
|
|
|
func encodeMessage(algo string, roundId int64, drawId int64, clientId int64, clientSeed []byte, nonce uint64, itemsRoot []byte, weightsTotal uint64) []byte {
|
|
var buf []byte
|
|
buf = appendUint32String(buf, algo)
|
|
buf = appendUint64(buf, uint64(roundId))
|
|
buf = appendUint64(buf, uint64(drawId))
|
|
buf = appendUint64(buf, uint64(clientId))
|
|
buf = appendUint32Bytes(buf, clientSeed)
|
|
buf = appendUint64(buf, nonce)
|
|
buf = append(buf, itemsRoot...)
|
|
buf = appendUint64(buf, weightsTotal)
|
|
return buf
|
|
}
|
|
|
|
func appendUint32String(b []byte, s string) []byte {
|
|
bs := []byte(s)
|
|
nb := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(nb, uint32(len(bs)))
|
|
b = append(b, nb...)
|
|
b = append(b, bs...)
|
|
return b
|
|
}
|
|
|
|
func appendUint32Bytes(b []byte, bs []byte) []byte {
|
|
nb := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(nb, uint32(len(bs)))
|
|
b = append(b, nb...)
|
|
b = append(b, bs...)
|
|
return b
|
|
}
|
|
|
|
func appendUint64(b []byte, v uint64) []byte {
|
|
nb := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(nb, v)
|
|
b = append(b, nb...)
|
|
return b
|
|
}
|
|
|
|
func hmacSha256(key []byte, msg []byte) []byte {
|
|
m := hmac.New(sha256.New, key)
|
|
m.Write(msg)
|
|
return m.Sum(nil)
|
|
}
|
|
|
|
func rejectSample(entropy []byte, subSeed []byte, enc []byte, W uint64) (uint64, []byte) {
|
|
var counter uint64
|
|
for {
|
|
R := binary.BigEndian.Uint64(entropy[:8])
|
|
M := (uint64(^uint64(0)) / W) * W
|
|
if R < M {
|
|
return R % W, entropy
|
|
}
|
|
counter++
|
|
cenc := make([]byte, len(enc)+8)
|
|
copy(cenc, enc)
|
|
binary.BigEndian.PutUint64(cenc[len(enc):], counter)
|
|
entropy = hmacSha256(subSeed, cenc)
|
|
}
|
|
} |