Eth-Lottery
一套基于区块链的抽奖算法,其目的是利用不可否认的随机数据来源进行透明和公正的抽奖计算。该算法基于以太坊区块链数据来计算获奖者。由于区块链的去中心化,不可篡改性和公开透明的特点,确保了抽奖结果的公平公正。
本文中所述的 Ethereum 区块链基于 PoW 共识
计算方式
准备数据
- 抽奖 ID、抽奖参与人数与奖品份数。
- 公示的所有参与用户的User ID Hash (计算方式在最后),将这些 ID 写入一个数组,并按 ASCII 码从大到小分配 0 开始的自增 ID。
- 开奖时间之后的第一个以太坊 Block Hash (包含
0x
前缀)。
计算初始开奖种子
伪代码, sha256
函数返回格式为小写 Hex 字符串, +
号为拼接字符串。
开奖种子 = sha256(抽奖ID + 参与人数 + 总奖品份数 + Block Hash)
计算中奖人
必须使用大数运算
- 将
开奖种子
的 16 进制转换为 10 进制。 - 将该十进制数字除以总参与人数,余数对应的自增 ID 即为中奖人,奖品按顺序发放。
- 如该用户已在中奖列表或不符合中奖条件,或者尚有未开奖品,则将
开奖种子
再次 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×tamp={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