第一次参与撸毛, 正好错过了 Quark ,不少朋友给我发了 Noss 的信息, 也是元旦最后一天, 所以晚上就第一时间研究了一下
https://x.com/fuergaosi/status/1741865877476417811?s=20
由于当时不了解 Nostr, 所以就直接在前端断点看代码,后面盲猜了一下 hash 函数是 sha256 发现没问题, 于是手动写了个 Python 脚本做复现, 因为之前测试挖矿的时候也写了 CUDA 版本的 Sha256, 所以分别测试了数据
import hashlib
import time
import json
tags = [
["p", "9be107b0d7218c67b4954ee3e6bd9e4dba06ef937a93f684e42f730a0c3d053c"],
[
"e",
"51ed7939a984edee863bfbb2e66fdc80436b000a8ddca442d83e6a2bf1636a95",
"wss://relay.noscription.org/",
"root",
],
]
config = {
"minterPubkey": "f6287f11c70c8767e95500cb67049847a72f83eec61b7b617e195009148c01df",
"deployerPubkey": "9be107b0d7218c67b4954ee3e6bd9e4dba06ef937a93f684e42f730a0c3d053c",
"deployEventId": "51ed7939a984edee863bfbb2e66fdc80436b000a8ddca442d83e6a2bf1636a95",
"difficulty": 21,
"amount": 10,
"tick": "noss",
}
def sha256(data):
first_hash = hashlib.sha256(data.encode()).hexdigest()
return first_hash
def check(e):
t = 0
for r in e:
n = int(r, 16)
if n == 0:
t += 4
else:
t += bin(n).count("0") - (len(bin(n)) - 2) + 1
break
return t
def generate_data(prev_id, hight, layer, nonce, timestamp=0):
return {
"kind": 1,
"created_at": timestamp if timestamp else int(time.time()),
"tags": tags
+ [
[
"e",
prev_id,
"wss://relay.noscription.org/",
"reply",
],
["seq_witness", str(hight), str(layer)],
["nonce", nonce, "21"],
],
"content": '{"p":"nrc-20","op":"mint","tick":"noss","amt":"10"}',
"pubkey": config["minterPubkey"],
}
real_data = '[0,"f6287f11c70c8767e95500cb67049847a72f83eec61b7b617e195009148c01df",1704119079,1,[["p","9be107b0d7218c67b4954ee3e6bd9e4dba06ef937a93f684e42f730a0c3d053c"],["e","51ed7939a984edee863bfbb2e66fdc80436b000a8ddca442d83e6a2bf1636a95","wss://relay.noscription.org/","root"],["e","000003b604303c7d08360843308b02d409bc117fa8cd8fe632e782e2b4489539","wss://relay.noscription.org/","reply"],["seq_witness","165995937","0xfdef9650e94ecc1481391cb94e59cbfb61a77dc59c9c1a2dc6691d27aacd2362"],["nonce","hww6osxomhp","21"]],"{\\\\"p\\\\":\\\\"nrc-20\\\\",\\\\"op\\\\":\\\\"mint\\\\",\\\\"tick\\\\":\\\\"noss\\\\",\\\\"amt\\\\":\\\\"10\\\\"}"]'
def hashdata(data):
text = json.dumps(
[
0,
data["pubkey"],
data["created_at"],
data["kind"],
data["tags"],
data["content"],
# no space
],
separators=(",", ":"),
)
return sha256(text)
import random
import string
def random_nonce():
result = ''.join(random.choices(string.ascii_lowercase + string.digits, k=13))
return result
if __name__ == "__main__":
nonce = "hww6osxomhp"
data = generate_data(
# Prev Node
"000003b604303c7d08360843308b02d409bc117fa8cd8fe632e782e2b4489539",
# Hight
165995937,
# Prev addcres
"0xfdef9650e94ecc1481391cb94e59cbfb61a77dc59c9c1a2dc6691d27aacd2362",
nonce,
# Timestamp
1704119079,
)
hash = hashdata(data)
print(check(hash))
if check(hash) <= config["difficulty"]:
print(nonce)
后续发现当时热度很高,就直接拿 Go 糊了一版本,后续看到了别人的脚本, 直接 CV 的一部分, 和前端一样使用 WS 跑一个链接池, 然后在 Golang 里面算 Hash 求解
整个代码可以分为三个部分
由于理论计算值是 16 ** 5 次方, 所以其实只要有 100w 次 hash 就可以,最早一版本由于每次 都要做 签名, 所以速度很慢,分别优化了 随机数和 Hash , 后面在 13900k 10 个 goroutine 就可以跑到 2/3 M/s 的算力
在测试没有问题以后, 就直接用 CUDA 对 hash 部分求解,在 Golang 里面用 Cgo 调用, 基本充分 Warm Up 的情况下可以做到 5ms 以内求解出来
可能由于脚本过多, 后续官方也对前端的接口和网页做了 CF 的检测和增加了 X-gorgon 算法
CF 部分比较简单, 由于级别不是开的最高, 所以伪装一下就能直接过了。