Eth-Lottery

一套基于区块链的抽奖算法,其目的是利用不可否认的随机数据来源进行透明和公正的抽奖计算。该算法基于以太坊区块链数据来计算获奖者。由于区块链的去中心化,不可篡改性和公开透明的特点,确保了抽奖结果的公平公正。

本文中所述的 Ethereum 区块链基于 PoW 共识

计算方式

准备数据

  1. 抽奖 ID、抽奖参与人数与奖品份数。
  2. 公示的所有参与用户的User ID Hash (计算方式在最后),将这些 ID 写入一个数组,并按 ASCII 码从大到小分配 0 开始的自增 ID。
  3. 开奖时间之后的第一个以太坊 Block Hash (包含 0x 前缀)。

计算初始开奖种子

伪代码, sha256 函数返回格式为小写 Hex 字符串, + 号为拼接字符串。

开奖种子 = sha256(抽奖ID + 参与人数 + 总奖品份数 + Block Hash)

计算中奖人

必须使用大数运算

  1. 开奖种子 的 16 进制转换为 10 进制。
  2. 将该十进制数字除以总参与人数,余数对应的自增 ID 即为中奖人,奖品按顺序发放。
  3. 如该用户已在中奖列表或不符合中奖条件,或者尚有未开奖品,则将 开奖种子 再次 SHA256,作为新的 开奖种子 ,回到第 1 步再次计算,直到所有奖品分发完成。

如何控制中奖结果?

首先,你需要拥有可观的以太坊算力(中奖概率和算力呈正比,甚至达到全网算力的 51%)参与挖矿。 如果你在开奖后最先挖出了新区块并且计算出自己没有中奖,则可以放弃这个区块的奖励(当前相当于 9000 美元左右,查看最新数据)不上报,然后在期望没有其他矿工抢先的情况下,自己挖出的下一个区块可以让自己中奖。 所以在绝大多数情况下,控制中奖结果或提高中奖概率是非常困难、成本相当高且仍然难以控制的。

实例

import hashlib
import datetime
import time
import sys
import requests

# 准备数据
lottery_id = "抽奖活动ID"
participants = ["参与者ID_01", "参与者ID_02", "参与者ID_03", "参与者ID_04", "参与者ID_05"]
prizes = 1  # 奖品数
data_time = '2023-4-30 15:14:00'  # 开奖时间


def get_blockHash(datatime: str) -> (str, int):
    time_f = datetime.datetime.strptime(datatime, '%Y-%m-%d %H:%M:%S')
    timestamp = int(time.mktime(time_f.timetuple()))
    sleep_time = timestamp - int(time.time())
    if sleep_time < 0:
        print("开奖时间,不能是过去")
        sys.exit(1)
    time.sleep(sleep_time)
    while True:
        res = requests.get(f"https://api.etherscan.io/api?module=block&action=getblocknobytime&timestamp={timestamp}&closest=after&apikey=47EN1HNR7M9MJ81G1BJN7EKX4P89FZUU7E")
        if res.json().get('message') == "OK":
            break
    block_num = int(res.json().get("result"))
    res = requests.get(f"https://api.etherscan.io/api?module=proxy&action=eth_getBlockByNumber&tag={hex(block_num)}&boolean=false&apikey=47EN1HNR7M9MJ81G1BJN7EKX4P89FZUU7E")
    return res.json().get('result').get('hash'), block_num


# 对参与用户ID哈希值进行排序
participants_hash = {hashlib.sha256((i + lottery_id).encode()).hexdigest(): i for i in participants}
sorted_participants = sorted(participants_hash, key=str.lower, reverse=True)

participant_ids = {i: p for i, p in enumerate(sorted_participants)}

# 获取开奖时间之后第一个以太坊Block Hash
block_hash, block_num = get_blockHash(data_time)
# 计算初始开奖种子
seed_str = f"{lottery_id}{len(participants)}{prizes}{block_hash}"
initial_seed = hashlib.sha256(seed_str.encode()).hexdigest()

# 定义中奖者列表
winner_list = []


# 判断是否符合中奖条件
def is_eligible(winner):
    # 这里可以根据具体的抽奖规则定义中奖条件

    # 这个例子中假设所有参与用户都符合中奖条件
    return True


# 计算中奖人
while prizes > 0:
    # 将种子转换为10进制数字
    seed_number = int(initial_seed, 16)

    # 计算余数对应的自增ID即为中奖人
    winner_id = seed_number % len(participants)
    winner = participant_ids[winner_id]

    # 判断是否需要重新计算种子
    if winner in winner_list or not is_eligible(winner):
        initial_seed = hashlib.sha256(initial_seed.encode()).hexdigest()
        continue

    # 将中奖者加入中奖列表,奖品数减少1
    winner_list.append(winner)
    prizes -= 1

    # 计算新的种子
    initial_seed = hashlib.sha256(initial_seed.encode()).hexdigest()

# 输出中奖列表
print(f"开奖块高{block_num},详细信息:https://etherscan.io/block/{block_num}")
print("中奖名单:")
for i, winner in enumerate(winner_list):
    print(f"第{i + 1}个奖品:{participants_hash[winner]}")


附录

User ID Hash 的计算方式

在设计算法时尽可能同时保证透明公开与用户隐私。 在计算中奖人时,计算 User ID Hash 的伪代码如下,其中 + 号为拼接字符串:

User ID Hash = sha256(User ID + 抽奖ID)

参考

https://github.com/WooMaiLabs/LotteryBot-V2-Docs