项目地址 https://noscription.org/

开始

第一次参与撸毛, 正好错过了 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)

Golang 脚本

后续发现当时热度很高,就直接拿 Go 糊了一版本,后续看到了别人的脚本, 直接 CV 的一部分, 和前端一样使用 WS 跑一个链接池, 然后在 Golang 里面算 Hash 求解

整个代码可以分为三个部分

  1. 使用 WS/Http 获取 区块信息和 最新的 EventID
  2. Sha256 求解
  3. 发送 event

Untitled

由于理论计算值是 16 ** 5 次方, 所以其实只要有 100w 次 hash 就可以,最早一版本由于每次 都要做 签名, 所以速度很慢,分别优化了 随机数和 Hash , 后面在 13900k 10 个 goroutine 就可以跑到 2/3 M/s 的算力

在测试没有问题以后, 就直接用 CUDA 对 hash 部分求解,在 Golang 里面用 Cgo 调用, 基本充分 Warm Up 的情况下可以做到 5ms 以内求解出来

Untitled

Untitled

X-gorgon 和 CF 拦截

可能由于脚本过多, 后续官方也对前端的接口和网页做了 CF 的检测和增加了 X-gorgon 算法

CF 部分比较简单, 由于级别不是开的最高, 所以伪装一下就能直接过了。