验证
自己查,别信我们的话
本站的每个宇宙都是比特币区块哈希的纯函数,而这个函数就是开源的 BNBBANG 引擎。验证一个比特币宇宙比验证一个 BNB 宇宙多一步:NFT 在 BNB Smart Chain 上,区块却在比特币上,所以你要在比特币上确认区块,其余一切在 BSC 上确认。五个步骤,不需要账号,不经过服务端。
合约为什么说 verified = false
铸造时,MirrorUniverse 把提交的哈希与 BSC 上的 blockhash(blockNumber) 比对,把结果存进 verified 字段。BSC 看不到比特币,所以这个比对对每个比特币区块都会失败,每个比特币宇宙的 verified 都是 false。合约记录下来,不会 revert。对一个比特币宇宙来说,这个标志位无论哪种取值都说明不了什么;下面的第 2 步就是取而代之的检查,任何人都能做。
第 1 步:在 BSC 上读出 NFT
在地址 0x1661086ee19cc8ef938e98ad9058e74fa870aa78(BNB Smart Chain,chainId 56)的 MirrorUniverse 合约上调用 universeOf(id)。BscScan 的「Read Contract」页签可以用,任何 RPC 客户端也行。结构体按声明顺序返回:
| 字段 | 对比特币宇宙的含义 |
|---|---|
blockHash | 比特币区块哈希,32 字节,即奇点 |
blockNumber | 比特币区块高度 |
mintedAt、minter | 在 BSC 上铸造的时间与铸造人 |
outcome | 合约十二种结局的索引,顺序与引擎相同 |
verified | 对比特币区块永远是 false(见上文) |
rarity | 0 到 4 = S、A、B、C、D |
cardOf(id) 返回服务端签过名的 cardHash,tokenOfHash(blockHash) 返回某个哈希对应的 token id(0 表示还没人铸)。记下 blockHash 和 blockNumber,下一步要用。
第 2 步:在比特币上确认区块
把这一对拿到比特币上。下面任一种都足够:
# 按哈希:页面会显示高度
https://mempool.space/block/<blockHash>
# 按高度:以纯文本返回哈希
curl https://mempool.space/api/block-height/<blockNumber>
那个高度的哈希必须等于 NFT 里存的 blockHash。相等,这枚 NFT 就是比特币宇宙,而且在你查看时这个哈希就在比特币主链上。不相等,这枚 NFT 就不是比特币宇宙;拿同一对去 BSC 上核,它会是一个 BNB 宇宙。任何浏览器或节点给出的答案都一样,例如 https://blockstream.info/api/block-height/<blockNumber>,或者在你自己的节点上 bitcoin-cli getblockhash <blockNumber>。别按前导零判断:早期比特币区块只有八个,一小部分 BNB 区块也以八个零开头。
本站自己也在 /api/btc/block/<height|hash> 提供同样的数据(带缓存),但这一步的意义就在于直接去问比特币。
第 3 步:从哈希重算常数
种子是区块哈希,不做任何变换。每个参数对「哈希 + 一个槽位字节」做一次 keccak256 抽一个槽位;档位来自槽位 255:
u_i = uint256(keccak256(blockHash ‖ uint8(i))) mod 1e9
tier = uint256(keccak256(blockHash ‖ uint8(255))) mod 1000
这就是 BNBBANG 的推导,原封不动;它在 bnbbang-engine 引擎目录下的 bnbhash.js 里,导出 derive(hash)。下面这段代码只用 ethers 就能为比特币创世区块重算前几个槽位和档位,你可以不依赖引擎独立确认这个公式:
// npm i ethers (v6)
const { keccak256, solidityPacked } = require('ethers');
// 比特币区块 #0,2009-01-03
const hash = '0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f';
const slot = (i, m) => BigInt(keccak256(solidityPacked(['bytes32', 'uint8'], [hash, i]))) % BigInt(m);
for (let i = 0; i < 4; i++) console.log('u_' + i, slot(i, 1_000_000_000));
console.log('tier', slot(255, 1000));
// 完整向量(按引擎的参数顺序),由引擎本身给出:
// const { derive } = require('./engine/bnbhash.js'); // 在 bnbbang-engine 的检出目录里
// const d = derive(hash); console.log(d.uInt, d.tier);
这些值必须与 derive(hash) 打印的一致。注意,现代哈希的十九个前导零(或创世区块的十个)在这里毫无影响:对「哈希 + 一个字节」做 keccak256,无论输入长什么样,输出都是均匀的。
第 4 步:跑引擎并比对
git clone https://github.com/q3579338/bnbbang-engine
cd bnbbang-engine
node tools/recompute.js 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
recompute.js 只凭哈希重建 23 个参数、结局、等级和 cardHash。推导只用整数,所以在任何平台上结果都相同。零依赖,Node 18 或更高。把结局和等级与本站的卡片、以及第 1 步的 outcome 和 rarity 对比;它们必须一致,而卡片上的徽章(创世、减半、难度周期首块、名块)不得改变其中任何一项。
第 5 步:重算 cardHash
cardHash 是服务端签名、合约存进 cardOf(id) 的指纹。它的计算方式与 BNBBANG 完全一致,推导版本 3:
cardHash = keccak256(abi.encode(
bytes32 blockHash,
uint32[22] uInt, // 第 3 步的 22 个参数槽位,引擎顺序
uint32 uG, // 槽位 200:引力耦合
uint32 uF1, uF2, // 槽位 201、202:两次参照系抽取
uint8 outcome,
uint8 rarity,
uint32 derivationVersion = 3
))
第 3 到 5 步的一条命令版本:从 BSC 读出 NFT,凭它的 blockHash 重算一切,再把参数、结局、等级和 cardHash 逐字节比对:
node tools/verify-onchain.js <tokenId>
被拯救过的宇宙(通过烧 BANG 微调过常数)由区块哈希加上写进交易里的操作日志重算,不需要服务端的存档。verify-onchain.js 唯一不检查的,是这个哈希是不是比特币区块;那是第 2 步,它需要的是比特币,不是 BSC。
引擎仓库附带 292 项引擎自检和 118 项链上比对测试。node engine/test.js 在本地运行前一组。
签名证明了什么,没证明什么
铸造需要服务端签名:合约只暴露 bangSigned()。服务端自己从比特币获取所请求高度的哈希(不信任客户端发来的哈希),不满六个确认、或保留区块未到开闸时间一律拒签,然后重算参数并盖章。签名地址可通过链上的 signer() 读出,也列在下方;任何人都能核对一枚 NFT 的签名是否恢复为该地址。协议连同可运行的验证器和真实向量在 docs/signing-protocol.md。
签名不允许服务端凭空造宇宙:签名的参数必须等于 recompute.js 从哈希算出的结果,而这正是 verify-onchain.js 检查的东西。它也不允许服务端凭空造比特币区块:哈希和高度明文存在 NFT 里,第 2 步会抓出任何比特币不承认的组合。
BSC 主网上的合约
chainId 56,2026-08-22 部署,与 bnbbang.com 共用。无代理、无升级路径、无 selfdestruct、无 delegatecall、无暂停。BTCBANG 自己不部署任何东西。
| 合约 | 地址 |
|---|---|
| BangToken(BEP-20) | 0xa5306b022f24b920aeafbff9f0dba1a391c4442d |
| MirrorUniverse(NFT) | 0x1661086ee19cc8ef938e98ad9058e74fa870aa78 |
| MirrorMarket | 0x5f1def4874613b64bac2272424f2041e1359b001 |
| BangNames | 0x6a732c3dd5555ee6c60c380e4e53b9a1f9cb17d6 |
| BangNames2 | 0x36e5d8358b9d97ab41ac9daf1ee962f4ec6001b0 |
| MirrorCrafted | 0x12ce81dbfbfae6614ae98a64a223a9369b43d30a |
| BangVesting(团队 1 亿) | 0xb3e1af9f2b57f9ed6d73a75a895f66f757e6e0e1 |
| BangPromo(上币费 1 亿;拟议的点火基金来源) | 0xaddd4a144b7371afc37cb590fa5570a0f2e72179 |
| 邀请返利金库(2 亿) | 0xfaec0579f6f15ec7e5048b0e11e273a6e40641c6 |
签名地址(链上 signer()) | 0x81F355093ef706bA5a6e74e9F286C28A479D33Ee |
实时检查:/api/health 报告服务端正在使用的链 id 和签名地址,必须与合约一致。/api/btc/tip 报告服务端当前看到的比特币高度与哈希,以及签名前要求的确认数。/api/market/status 显示市场索引落后链多少。
四件不是去信任的事
- 铸造需要服务端盖章。合约层面的要求,不是前端的选择;签名地址在链上,盖章的参数可重算。
- 服务端决定比特币区块何时可铸。六个确认,五个保留区块(创世和四个减半)只在公示的开闸时间之后。它改变不了一个区块的宇宙长什么样,只能决定今天签不签。
- 邀请返利经人工核对后发放。一级 10%、二级 5%,按被邀请人的铸造奖励计;女巫模式不发。每笔链上 grant 都带 reason。
- 存在几个 owner 旋钮,全部有界。三个免费铸造开关只能收紧;拯救抽成默认 0(上限 20%);市场费用上限 10%;每次铸造发行量在 100–2000 之间。