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) } }