
图片来源:Citadel21
TL;DR — 快速摘要
- 2026年7月30日晚,攻击者花了约四十分钟转移他人的比特币,截至8月4日确认盗走1,596枚(约7,300个地址,逾1亿美元);没有钓鱼、没有恶意软件、没有接触设备——因为钱包在诞生那一刻就泄露了秘密
- 被攻击的钱包是Coldcard。2021年3月1日的一次提交把种子生成从久经考验的Trezor加密库迁到CTO Peter D. Gray以化名switck自写的libngu库,有效熵在Mk2/Mk3上跌到约40位,Mk4/Mk5/Q约72位
- 根因是一个字符的守卫错误:代码检查「设置是否已定义」而非「是否已启用」,于是本应禁用软件随机数的标志(设为0)反而召回了软件模拟器;靠掷骰子(50次以上)或强口令生成的种子不受影响
- 2025年5月James O'Beirne曾点名警告该库并建议移除,却被以「坏了我们早就知道」驳回;「不要信任,要验证」的社区文化,恰恰在钱包唯一核心的功能上——没人验证——搁置了五年
2026年7月30日夜里,一名攻击者花了大约四十分钟转移他人的比特币。这些币来自数千个地址,一波接一波。到8月4日,研究机构Galaxy确认的赃款为1,596枚比特币,来自约7,300个地址,分三波攻击和十四起规模较小的事件;据估计,若把疑似案件算进去,总额将接近2,000枚比特币,约合1.3亿美元。仅确认数字就早已超过1亿美元。没有人被钓鱼。没有涉及恶意软件。没有任何设备被碰过。攻击者一样都不需要,因为这些钱包在多年前诞生之时,就已把自己的秘密告诉了全世界。
这些钱包是Coldcard。在过去十年的大部分时间里,由加拿大公司Coinkite制造的Coldcard,是严肃的比特币玩家互相推荐的那个硬件钱包。它是密码朋克的选择:自带掷骰子功能、以安全为先的声誉,还有一位会大声告诉你其他人都做错了的创始人。它是偏执的选择,而偏执正是其卖点。
这场清扫揭示的是:五年来,这个偏执的选择一直在用一个并不随机的随机数生成器生成种子。缺陷并不离奇。它是代码里的一行中的单个字符,摆在明处,在一个公开的代码仓库里,静静等待。这就是它如何出现、是谁放进去的、谁曾试图警告他们、以及一个把整个身份建立在「不要信任,要验证」这句话上的社区,为何会花五年时间去信任而不去验证的故事。
第一部分:种子
比特币钱包是一个非常巨大的数字。控制你币的私钥,是从一个大到凭蛮力猜测纯属幻想的空间里取出的一个特定值。一组十二个单词的恢复短语编码了该空间的128位。要暴力搜索它,你需要的时间比宇宙剩余的时间还长。
这层保护完全建立在一个假设之上:这个数字必须真正不可预测。它必须来自真正的随机性,即密码学家所说的熵。一个硬件钱包有一项比其他任何事都重要的工作:从物理源抽取一个真正随机的数字,绝不让任何更弱的东西取代它。其余一切都是装饰。
Coldcard的芯片上有一个硬件真随机数生成器,也就是它应该使用的物理源。Coinkite长期以来一直宣传该设备对随机性的审慎处理,包括用掷骰子添加自有熵的选项。在纸面上,种子是安全的。
在受影响的固件上,对随机性的请求仍然发出,但不再到达硬件。根据发布[第一份详细技术分析](https://engineering.block.xyz/blog/predictable-rng-fallback-and-32-bit-reseed-in-coldcard-firmware)的Block工程团队、以及[独立追踪到同一条路径](https://wizardsardine.com/blog/coldcard-rng-vulnerability/)的安全公司Wizardsardine的分析,本应从硬件生成器取数的调用,悄然解析到了软件上,因此那个本用于把物理噪声与备份混合的例程,实际上是在混合两个软件生成器。其中一个,位于libngu库内部,始于写死在源码里的常量,在所有Coldcard上完全相同,并且[如Wizardsardine所言,贡献的熵为零](https://wizardsardine.com/blog/coldcard-rng-vulnerability/#:~:text=Entropy%20contributed%3A%20zero.)。
另一个则以芯片的唯一标识符和内部时钟状态为种子,这些值既不保密也不真正随机。种子最终依赖的,是一小撮可被猜到的状态,而这一小撮有多小,取决于你拥有的是哪款Coldcard。
Coinkite自己在其「[熵问题技术深度解析](https://blog.coinkite.com/entropy-technical-backgrounder/)」博客文章中给出的估计是:Mk2和Mk3的有效熵跌至约40位,较新的Mk4、Mk5和Q约72位,而钱包本应提供128位。
40位并不是一个无法搜索的幻想。知道钱包公开地址的攻击者,可以重新生成那一小撮可能的种子,逐一与区块链比对,找到匹配项,然后拿走币——全程无需看到设备。
掷骰子救了用过它的人。用五十次以上私人掷骰结果构建的种子,混入了软件无法污染的真实熵,而且[Coinkite表示](https://blog.coinkite.com/coldcard-mk3-seed-generation-warning/)这些种子不受此缺陷影响。
一个强大而独特的口令也是抵御这种攻击的护身符。几乎所有其他在受影响固件上生成种子的人都被暴露了,而他们大多毫不知情。
这家公司围绕这个承诺建立了一整套指南。Coldcard自己的文档里有一份[Paranoid Guide(偏执指南)](https://coldcard.com/docs/paranoid/),为最注重安全的设置提供教程:设备终其一生都不得连接任何种类的计算机,从墙插取电,只靠来回移动SD卡交换数据。「偏执」不是批评者发明的词,而是公司自己对最审慎用机方式的称呼。
最残忍的部分,正是Coldcard成名之处。它的标志性特性是永远不需要接触电脑。你可以让它离线运行,用SD卡来回搬运交易,这样即使你的电脑满是恶意软件,也没有任何东西能穿过这道鸿沟碰到密钥。在[2021年2月的Stephan Livera播客](https://www.stephanlivera.com/252)上,NVK详细解释了这个逻辑:[他说,气隙(air-gap)的意义在于](https://youtu.be/Jyd1NfV6Fj8?si=0mcet0rRhwR-rL7R&t=245),即使攻击者知道存在某个漏洞,他也无法远程进入并取走他要找的东西。这就是整套安全模型,而面对大多数威胁它是站得住脚的。但在这次事件里它毫无用处。熵缺陷不需要伸进来,也没有东西可取。种子从公开区块链上就已可被猜到,所以攻击者根本不必碰设备,连不连网都一样。这个让Coldcard感觉最安全的特性,在面对真正到来的失败时,完全无关紧要。
第二部分:一次提交
缺陷在一天之内进入代码库:2021年3月1日,[一次提交](https://github.com/Coldcard/firmware/commit/b18723dddb6d751c39978e4364b56b2414f68b47)。它是公开的,任何人都能读。提交信息是「_First pass w/ libNgU_」,标识符以b18723d开头,改动120个文件,新增2,766行、删除2,722行。作者是GitHub账号doc-hex。
那一次提交同时做了好几件事,而它们的组合正是这个故事的核心。它删除了名为COPYING的文件,里面是GPL第三版许可证全文。它移除了一个指向Trezor衍生加密库的子模块——那是Coldcard赖以构建的、历经数年打磨的成熟代码。它新增了一个指向名为libngu的库的子模块,该库归一个叫switck的账号所有。
而同一批改动还重写了生成钱包种子的文件,包括板级配置头文件和一个新的随机数文件。
于是,在一天之内的一次提交里,Coldcard拆掉了继承来的久经考验的加密库,甩掉了随之而来的许可证,并把种子生成接到了一个由别人编写的新库上。而那个「别人」,结果根本不是别人。
这次迁移并非不加区分,而这是最奇怪的地方。固件中七处从硬件随机源取数的地方,有五个被原样保留,至今仍读取硬件生成器。只有两处被移到了新路径上。这两处是钱包种子和供给它的共享随机模块:设备里最需要安全保护的两个随机性来源。
首先[从公开的git历史中记录了这次迁移](https://gist.github.com/afilini/b7d13bd2d7deaf8f23d30213a801e3c5)的开发者Alekos Filini,对它的含义措辞谨慎,当我直接问他时,他依旧谨慎。他指出,同一个开发者在固件自己的代码里写下了正确的守卫,却在libngu里写了坏的那个,Filini称之为一个错误。我问他,他的分析是否显示种子路径是被故意迁移的。他不愿走那么远。他说,这个发现更恰当的说法是:两条路径与其余路径被区别对待了。
这种区别对待是否出于故意,他说他无法判断,因为他无法确定那不是意外。他提供了两种解读,自己却不作选择。他说,这位开发者可能是在代码里跳来跳去,没多想。又或者这次迁移是有意且善意的:他可能认为自己的新库更好,想用它,真心相信它会改善设备的熵,而不是毁掉它。
这就是发现这个事实的人所能走到的最远处。代码本身无法解释的是:为什么设备里最重要的一次调用,属于被迁移的那两处之一,而文件擦除和备份却留在它们仍在使用的硬件上。种子路径被区别对待,这是一个事实。它意味着什么,记录并没有定论,而那个把历史读得最细的人拒绝下定论。我在「回应权」邮件中问Coinkite:为什么种子路径和供它的模块被迁移,而其他五个消费方留在硬件上。公司在截止日期前没有回答这个问题。
还有一枚时间戳属于这一部分,它也是公开的。2021年3月9日,[4.0.0 beta发布](https://x.com/COLDCARDwallet/status/1369404716539670534?s=20)的那天,一位用户读了发布说明,注意到关于BIP39代码被替换的那一行,[直接发问](https://x.com/xavierfiechter/status/1369412918228443137),并贴出一张放大的截图,圈出了原话:「所有crypto和bip39相关代码已替换。」
DocHex当晚11点23分在公开场合回答,这段对话至今还在。_「是的,换成了功能等价的代码。所有同样的功能,外加一些BIP39改进,因为反正我们在里面了。」_
用户又问,公司关于验证掷骰子的指南是否仍然有效。_「是的。百分之百。」_DocHex回答。在骰子指南上他说对了;掷骰子是那次回归从未碰过的唯一种子路径。在等价性上他错了。替换并不功能等价,因为对设备最重要的功能而言,它不再可靠地到达硬件。这段对话保留下来的是:DocHex在提交八天后、发布八天前,公开给出的那份笃定——而提问的用户正好指对了地方。
第三部分:switck
到目前为止,这个故事里出现了两个名字。一个是doc-hex,创作那次提交并公开为之回答的账号。另一个是switck,拥有这次提交所投靠的libngu库的账号。2020年10月,switck曾[在X上宣布那个库](https://x.com/switck/status/1318909664281636867),配文「_我做了个有用的东西_」,并附上仓库链接。
几天前[他曾给DocHex写信](https://x.com/switck/status/1317230987294740480),说他「_在做另一个比特币库,说不定哪天对@COLDCARDwallet有用_」。
DocHex和switck是同一个人,这个人就是Peter D. Gray,Coinkite联合创始人兼首席技术官。这不是猜测。开发者Dylan LeClair通过[比对几十次提交上switck、doc-hex和Gray本人名字下的GPG加密签名](https://x.com/DylanLeClair/status/2084536263109906591)确认了这一点。
比特币开发者James O'Beirne[验证了这个匹配](https://gist.github.com/jamesob/ca9b4ca384969b4cfd62813419854d69)。
Foundation的Zach Herbert[独立指出](https://x.com/zherbert/status/2084647957526167853)两个账号都绑定了末两位相同的电话号码,还有一个[Clarity.fm主页](https://t.co/AfOcoFbcFx)把Gray列为联合创始人兼CTO。
这个身份是过度确定的。发表前我问Coinkite是否质疑switck和doc-hex账号属于Gray。我也通过X私信把问题发给了Novak(@NVK)和Gray(@switck)。Novak的私信被读过了,但没有回复。一位Coinkite支持专员确认已把邮件问题转交给公司。没有答复。Coinkite没有否认这个身份,而在有机会否认时,它没有。
这里有一个细节,容易被错过,一旦看见就无法视而不见。2020年10月21日,switck的仓库上开了一个题为[「PR's accepted?(接受PR吗?)」](https://github.com/switck/libngu/issues/7)的issue。开这个issue的是doc-hex——Gray的Coinkite身份,被仓库标记为贡献者,却装作局外人写道:「_欢迎来到开源。我看这是MIT许可的,我们想在某些项目里使用其中一些(其实差不多全部)代码,比如@Coldcard。我们以PR形式提交代码的话你们会接受吗?_」
而switck——Gray的另一个身份,被标记为所有者——回答_「没问题」_,把issue当作已完成关闭,然后片刻之后补了一句_「开玩笑的。看PR内容而定。」_
从开场提问到收尾俏皮话,这段对话大约持续了八分钟。Gray在公开场合向自己征求许可,又自己批准,为的是确立libngu是以外部贡献的身份进入Coldcard的。
它不是。那是CTO自己的代码,打扮成第三方的。我问Coinkite这段对话的目的,以及它是否意在把libngu呈现为第三方库。公司没有回答。
这场虚构并没有停留在GitHub上。当那次重写在三月份发货时,[固件4.0.0的官方发布说明](https://blog.coinkite.com/version-4.0.0-released/)用这样的词句为这次迁移记功:「_非常感谢@switck的新库!_」
公司在自己文档里,以他的化名感谢自己的首席技术官,仿佛他是外部贡献者——这份致谢至今还在。
那场编排的对话证明的事情很窄。它证明Gray为一个属于自己的库,制造了一个「保持距离」的出身。它并不证明他知道这个库是坏的,也不证明他制造距离的动机。
第四部分:许可证
要理解为什么一家硬件钱包公司的CTO会想为自己的库演一场假的许可戏,你得回到2020年夏天,回到一个竞争对手身上。
2020年7月28日,[Foundation Devices发布了Passport](https://foundation.xyz/blog/introducing-passport),一款新的硬件钱包。Foundation公开表示Passport构建在Coldcard固件之上——当时该固件以GPL第三版许可证发布。GPL是一种copyleft许可证。它授予任何人学习、修改和在此基础上构建代码的自由,条件是他们也以相同的条款发布自己的版本。Coldcard自己在2018年就使用过这种自由,构建在Trezor的GPL代码之上。现在一个竞争对手把它用在了Coldcard身上。
两天后,2020年7月30日,Coinkite首席执行官NVK发表了他的反应。这条帖子写道:「_我真后悔给Coldcard选了GPLv3。现在我们有了个克隆(如果它真出货的话),代码/金钱上零贡献。克隆就是有人拿走最难的活儿、只改了UI的那种东西。选GPL是我们的错。我们会在以后的更新里大改特改这一点。」_

这条帖子后来已从NVK的账号上删除。其措辞由[删除前的一次抓取](https://x.com/zherbert/status/2083377265593692242)保留在此,删除本身也已确认:X的界面现在在被引用的原帖位置显示「_此帖已被作者删除_」。
NVK在那条帖子里承诺的事,公司照做了,整个过程记录在Coinkite自己的仓库里。2020年11月18日,[Coldcard添加了一个新许可证](https://github.com/Coldcard/firmware/commit/b6b9191145d37fbb6d754597350653141596dd12):MIT加Commons Clause,这个组合允许阅读代码,但禁止他人销售大幅基于它的产品。Commons Clause明确表示,在它之下的软件不是开源软件。[固件3.2.1版本](https://github.com/Coldcard/firmware/blob/master/releases/History-Mk3.md)(2021年1月)在发布说明里宣布了这一变更:许可证从GPL改为MIT加Commons Clause。
按这些术语的通常含义,Coldcard正在变成「源码可得但受商业限制」而非自由软件。
最后一块要移除的GPL代码,就是Trezor衍生的加密部分。这正是2021年3月那次提交做的事。而被引入以取代它的库——switck的libngu——本身也为此改了许可。2021年2月5日,在被接入Coldcard的三个半星期前,[libngu的许可证被更改](https://github.com/switck/libngu/commit/0f328b7b1aabbc1cc4a237a9b9abea49fa83c267),从标准MIT许可换成了一份名为《Licensed for Bitcoin Only(仅供比特币使用)》的自定义许可,新增一项条款,禁止在任何处理比特币以外加密货币的系统中使用它。
这个个人业余项目,在即将成为Coldcard种子生成基石之前,被装上了一套量身定做、针对竞争对手的许可证。
NVK在公开场合把动机说得很直白。2020年8月6日,在他那条关于GPLv3的帖子被删一周后,[他发推](https://x.com/nvk/status/1291385847749791744)说自己_「可能正在搞一个全新的许可证来对付开源掠夺行为」_,并且_「想把它叫做Fuck The Leeches License,FTLL」_。
Coldcard最终搭载的许可证没那么粗俗,却更有效。但「复用开放代码是一种应当封杀的掠夺」这种情绪,才是驱动这次大改的东西。而这次大改,正是带着那个bug上路的。
证据在这里很强,也在这里止步。从不可篡改的git历史中可以证明的是:移除GPL和Trezor血统、引入有缺陷的种子路径,发生在同一次提交里,是同一场由许可证驱动的大改的一部分。无法证明的是许可证动机导致了那个bug。整理了这段年表大部分内容、且有充分理由把这一点往重里说的Herbert,没有这么做。[他自己的话](https://x.com/zherbert/status/2082993276324319713)是:「_我们不知道许可证压力在多大程度上影响了这次重写的范围或时间线。我们能确定的只是:熵bug是在同一批120个文件的提交里被引入的,而那批提交正好移除了旧的GPL代码依赖。」_
这次重写也有真实的技术目标。Coinkite援引了迁移到Bitcoin Core的libsecp256k1、更快的加密例程和可复现构建。
一场部分由摆脱许可证的欲望驱动的仓促重写,承载着真实的工程抱负,偏偏引入了一个谁都没发现的灾难性缺陷:这是对「压力下的疏忽」的描述,而不是对破坏的描述。值得直说,因为诱惑是反过来的:许可故事本身没有任何东西表明有人蓄意削弱钱包。代码提出的那个问题——上一节里关于哪些文件被移动的问题——是另一回事,它仍然悬而未决。
第五部分:一个字符
这个bug本身小得近乎令人失望。[libngu里有一个守卫](https://github.com/switck/libngu/commit/f19de0527a49e560203102288ae4bc9740a32d96),一段唯一目的就是阻止这类事情发生的代码。它的大意是:如果硬件随机数生成器不可用,就拒绝构建并抛出错误「_get a HW TRNG plz(搞个硬件真随机数生成器来)_」。
意图再清楚不过。作者希望固件大声失败,而不是在没有真正随机性的情况下发货。
这个守卫因一个字符之差写错了。它用的检查是「某个设置是否被定义」,而不是「是否被启用」。Coldcard的板级配置确实定义了这个设置,但把它定义为零,即关闭。因为该设置存在本身,守卫看到了它,于是保持沉默。构建通过了。错误从未触发。而种子生成在伸手要硬件随机数时,解析到了软件模仿品上。[正如Wizardsardine总结的](https://wizardsardine.com/blog/coldcard-rng-vulnerability/#:~:text=The%20safeguard%20that%20was%20supposed%20to%20be%20working):一个字符横亘在这个安全屏障与其使命之间。
正确的硬件例程一直存在于固件里。它被用于其他事情,比如为备份文件生成密码。审阅代码的人看到它在那里,便断定钱包没问题。而根据披露后流传的概念验证(PoC)分析,没有人验证过种子路径在创建钱包的那一刻实际到达的是哪个例程。安全的代码就在那里。没人检查它有没有运行。我问Coinkite:公司里有没有人曾通过运行时而非阅读源码的方式,验证过创建钱包时种子路径到达的是哪个生成器。公司没有回答。
Gray自己对bug的解释,发表在Coinkite的「[熵问题技术深度解析](https://blog.coinkite.com/entropy-technical-backgrounder/)」里,描述的正是那种会产生这种结果的错误假设:他把配置标志设为零,以为钱包两种内置生成器都不需要,却没明白这个设置反而会召唤出软件那个。
守卫无法告诉你的是:为什么种子路径一开始就被引向它,而其他硬件消费方却被留在原处。那个问题和这个问题不必有同一个答案。
这个错误是个小错误。犯下它的人却不是小人物。早在比特币、早在Coinkite之前,Peter Gray的整个职业生涯就花在这个bug所在的层面:裸机微控制器上的嵌入式固件,与硬件钱包内部那类又小又缺资源的小芯片同类——8位和16位的Motorola部件、32位的ARM。他为没有任何操作系统的硬件编写设备驱动、移植操作系统和网络协议栈。他在几千字节的内存里手写USB协议栈。他用C和汇编,跨多种架构,干了几十年,在那个软件必须与硅片严丝合缝、否则就完全不通的逼仄空间里。这不是他顺路拜访的领域,这是他最熟的领域。
这正是那个守卫让人无法移开视线的原因。一个因为询问「设置是否被命名」而不是「是否被启用」而失败的检查,恰恰是那种本该被那种专业水平的仔细阅读抓住的错误。它是那种Gray——所有人里最该抓到的——最有能力看到的东西。而五年来,在一个整个承诺就是安全的设备里最需要安全保护的库中,没有人看到它,包括这家公司里最有能力看到它的那个人。
第六部分:那次警告
这个缺陷并没有五年无人看见。2025年5月,比这场清扫早一年多,James O'Beirne着手审计的正是Coldcard固件的这一部分。他想确凿地知道钱包的随机性来自哪里。他一路追到了libngu。他标记出硬编码的Yasmarang常量过于宽松、是糟糕的选择,并建议团队libngu没有必要,可以用标准加密库取而代之。他还亲自拆解了设备的随机性机制,而他告诉我说,最让他不安的是:他无法确定MicroPython软件回退没有被使用。他相信他向团队表达过这个担忧,尽管他记不清原话。
O'Beirne不是怀有敌意的外人。他是受人尊敬的比特币开发者,而且据他自己说,他曾经是NVK的朋友。他指名道姓、直接提出了担忧。一个名字恰如其分的Signal群组「_LNGU Clean up_」,日期2025年5月23日,NVK和Gray都在其中,记录着这场对话确实发生过。
当我问O'Beirne他得到了什么回答,他告诉我说回答来自Gray或另一位Coinkite工程师,而其内容一直留在他心里:如果那玩意是坏的,他们好几年前就会知道了,因为设备会生成那么多坏密钥。这个推理假设一个坏掉的生成器会自我暴露。熵失败的特别残忍之处在于:它不会。一个弱种子看起来和强种子一模一样——对它的主人、对它的制造者都一样——直到有一天别人把它推导出来。O'Beirne没有再追问。他告诉我,他把这句打发话当成了保证:代码确实配置对了,只是写得让人困惑而已。他说,那现在看是_「一个巨大的错误」_;他太过于顺从公司「一切正常」的保证了。十四个月后,钱包开始被抽干。我把O'Beirne对那次对话的描述放到Coinkite面前,问公司是否否认。它没有回答。
Coinkite对O'Beirne的回应不是随口的敷衍。那是一条原则。四年前,[在Stephan Livera的播客上](https://www.stephanlivera.com/252),NVK阐述了他关于「谁有权决定一个漏洞有多严重」的哲学。他说,为一个bug定级的人,是目标厂商——厂商才是关于它的最终事实来源,哪怕厂商轻描淡写。
换一种读法,这话其实没错:制造者最懂代码。对照O'Beirne在2025年5月的遭遇来读,它就是一句话说完了整个问题。当厂商同时是「某个警告是否值得采取行动」的唯一仲裁者时,一次错误的驳回就没有安全网。O'Beirne指出了确切的缺陷。而厂商——那个终极事实来源——轻描淡写了它。
O'Beirne值得倾听,恰恰因为他在哪里止步。他比几乎任何人都更仔细地看过这些东西,他本人被回绝过,而他说,化名和那次驳回让他留下——用他自己的话说——「很大的可能性,有些东西不对劲」。他没有说那是故意的。当最有理由、也最有资格提出更大指控的人拒绝提出时,那很可能就是证据所支持的真实边界。
那次警告有一个尾声,而且是Coinkite自己发布的。2026年8月,失窃之后,公司发布了[一份设备历史上每一次安全审查的年表](https://coinkite.com/historical-disclosures)。它记录道,在2026年5月1日至6月26日之间——也就是O'Beirne点名libngu整整一年后——一次AI辅助的固件审查产出了八十五条候选发现,每一条都做了分诊,然后审查关闭了。
攻击在三十四天后开始。审查没有抓到熵bug。Coinkite表示,它此后用几款前沿AI模型测过这些代码,没有一个抓到它。按公司自己的说法,这个bug活在子模块之间的边界上,在它的审查所瞄准的代码之外。而O'Beirne一年前就指名道姓地瞄准了正确的代码。
第七部分:无人验证之事
时间线里埋着一个冷峻的笑话。2021年10月10日,缺陷发货七个月后,官方Coldcard账号被要求定义一条安全术语:「退休攻击」(retirement attack)。[它答道](https://x.com/COLDCARDwallet/status/1447213375398846473):「_就是项目制造者可能在熵生成里留个『bug』,以便日后取回_」。
「退休攻击」是硬件钱包安全里一个真实存在的、有名字的概念:制造者故意削弱随机性,以便日后抽干所有人。Coldcard当时描述的,正是它自己固件已经意外携带的那种失败的精确形态,而且是作为已知威胁来描述的。重要的是不要过度解读。描述一种教科书式攻击不是承认实施过它,每个严肃的钱包制造商都会警告这一类事情。但这个反讽精确得刺眼,它指向了真正的指控——而那份指控根本不在于某个人的意图。
同一个反讽还有第二个版本,它更锋利,因为是NVK自己主动说的。[2022年的Citadel Dispatch](https://www.youtube.com/watch?v=JY9zmS5n8DI)上,在即兴聊到一个老练的国家行为体可能如何悄悄杀死一个开源项目时,他带着欣赏描述了那套剧本:「_你做出非常糟糕的决定,他说。你把软件做得糟透了,没有文档,然后你只管把以后能用上的bug全都加进去_」。
他在描述破坏。而等他说出这话的时候,他自己的固件早在一年多前的一次重写里做出了一个糟糕的、没有文档的决定,而且那次重写带着的bug,将在他命名这个模式大约四年后被人利用。这里面没有任何东西能证明意图,相似之处也许只是这件事残酷的对称性。但这场灾难的精确形态,在它到来之前,已经被提起过两次。
这个反讽最古老的版本早于缺陷本身。2020年12月,也就是引入这个bug的提交的十周前,NVK与Michael Flaxman在X上辩论种子生成。Flaxman是一名安全工程师,主张可验证的掷骰子,理由是——如他在那条讨论串里所说——信任随机数生成器的替代方案是可怕的。NVK对机器很放松:[「_我赌的是人们先把自己坑出比特币,然后才有厂商尝试退休攻击_」](https://x.com/nvk/status/1341217750019149824),他写道。「_或者人们也可以直接用骰子 ;)。_」然后:「_我想我们都同意人们不应该信任厂商的RNG,我们只是对解决方案有不同的偏好。我只是觉得人们会把熵搞砸。_」
这场赌局的每一个分句都被反转了。搞砸熵的是厂商的生成器,而不是用户;而认真大量掷出的骰子,救的是那些用了它们的人。他在原则上是对的。任何人都不该信任厂商的RNG,Coinkite的尤其不该。和其他反讽一样,这个也只能证明「接近」,而且坦诚谈论厂商风险正是负责任的厂商该做的事。这段对话记录下的是:在重写发货的十周前,即将重写它的那家公司非常清楚种子路径的利害关系。
第八部分:审视如何被惩罚
那个声誉确实在起作用,而且被激烈捍卫。2020年Foundation Devices按照GPL许可证邀请任何人的方式,在Coldcard的开放代码上构建钱包时,[NVK并没有把它当作普通竞争](https://x.com/zherbert/status/2084269925632217297)。在官方Coldcard Telegram群里,他称之为_「亲和力诈骗」(affinity scamming)_,说_「他们做的只是改了UI」_,宣布该项目_「闭源」_,并用一个双关警告说它_「会在用户被自己都能做安全的人攻破(pwnd)时以眼泪(tiers)收场」_。他在同一个频道别处还写道:_「纯粹的克隆,一场导入库的盛宴」_。这些是他的原话,在他自己的社区里,说的是一个行使他的许可证所授予自由的竞争对手。
同一个模式里有一个更尖锐的反讽,因为两端都在记录里。2018年,当是Coldcard在别人的GPL代码之上构建、Trezor联合创始人批评Coldcard重命名借来的文件时,NVK援引的正是他后来会抛弃的那条原则为自己辩护:「_我们仔细研究过许可问题,并且尊重GPL_」,他当时写道。
两年后,站在同一个行为的两端,对GPL的尊重变成了要被自定义许可证封杀的「水蛭掠夺」。规则没有变,变的只是他站在分岔路的哪一边。
把这些拼凑起来的重点不是NVK很粗鲁——虽然他确实粗鲁。重点在于好斗是有功能的。它让批评Coldcard变得昂贵,而最清楚的例子,是当靶子不是竞争对手,而是验证机制本身的时候。
2023年6月,WalletScrutiny——一个唯一目的就是检查钱包公开代码是否与出货二进制匹配的项目——[报告](https://x.com/WalletScrutiny/status/1671775020928966658)它无法复现某些Coldcard构建。
NVK的回应是发帖说WalletScrutiny「[_要么无能要么恶意_](https://x.com/nvk/status/1671237705517105152)_」_,几分钟后又说「_[是不是该起诉他们了?](https://x.com/nvk/status/1671238674057502721)_也许其他被他们审查无能所诽谤的项目也该加入_」。
公司的GitHub issue跟踪器被禁用了,于是这场技术纠纷变成了社交媒体上的互相辱骂。然后,一位两边都不沾、声誉很好的Bitcoin Core开发者Carl Dong花了一天时间处理这件事,说他不愿看到双方陷入恩怨。他复现了问题,追到构建脚本在文件被预下载时没能设置时间戳变量,确认构建现已修复,[并总结道](https://x.com/carl_dong/status/1671974473958826015):_「没有人想搞恶意,这是一场诚实的误会」_。审计者是对的。缺陷真实存在、性质良性、而且已经修复。那个「_无能或恶意_」的指控,二十四小时内就被一位独立专家驳倒了。
公平起见要指出,NVK在那次事件中的行为并非铁板一块。同一段时间里他也提供了赏金:给最先能录视频、可复现地构建出固件的人每人一百万聪,这是一个支持验证的姿态,而且Coldcard确实发运了修复。他的回应是矛盾的——一口气慷慨、下一口气威胁起诉——而不是铁板一块地敌视。但敌意明确指向了一个做可复现构建的可复现构建项目。
这种事以前也发生过。2020年底,Shift和Nunchuk的研究人员负责任地披露了Coldcard验证多重签名设置方式中的一个真实缺陷:这个bug可能让一台被攻破的电脑诱骗设备接受一个用户其实并不属于的钱包。Coinkite迅速修复了它,[在3.2.1版本里](https://blog.coinkite.com/version-3.2.1-released/),并点名致谢了那位研究人员。
一年半后,在[Citadel Dispatch第8期](https://www.youtube.com/watch?v=JY9zmS5n8DI)上谈及此事时,NVK毫无保留地认账:「_百分之百是我们的错_」。而在同一口气里,他这样形容那些报告问题的人:「_就像公关恐怖主义_」。他别处还称披露的研究人员为「_酸溜溜的婊子_」,并这样解释他拒绝付给他们赏金的原因:「_我不跟恐怖分子打交道_」。
在那些秽语之下,他其实有一个真实的论点:竞争对手可以把披露武器化——先自己推出一个安全功能,然后把对手没有它宣传成bug;他也有一个合理的委屈:有些报道把这个缺陷夸大成远程攻击。但「恐怖分子」语言对准的是一群默默报告了真实漏洞的研究人员,那个漏洞被他自己公司修复了,而且他自己说百分之百是他的错。
这个模式没有被那些最有理由为公司辩护的人忽视。Ben Perrin(即BTC Sessions),一位长期担任Coldcard付费赞助商、多年来向观众推荐该设备的知名硬件钱包教育者,[在失窃后写道](https://x.com/BTCsessions/status/2085698751558009085):O'Beirne曾被无视,其他提出担忧的人则被斥为危言耸听,或者被拉黑。他写道,NVK以高人一等的姿态对待整个圈子,而且有时对竞争项目充满攻击性。
出自一个推广过该产品、并且违背自己过往言论的人之口,这不是对手的证词。
这种氛围是否阻止了本可能更早抓到熵bug的审视,无法证明,也不该断言。能说的是:审视没有发生;唯一认真看过的研究人员被挥手打发走了;而那个缺陷最终不是靠仔细审计发现的,而是——根据披露后几天的证据——靠人们把现代代码分析工具对准一个公开仓库,几分钟内就找到了五年顺从所错过的东西。
第九部分:安然入睡
Coldcard被卖给、被推荐给、被信任为「为凡事都要验证的人而造的钱包」。它的文化建立在「不要信任,要验证」这句话之上。在实践中,那个世界的验证渐渐变成了「运行你自己的节点来检查区块链」。几乎没有人能审计签名设备的随机数生成器,而且几乎没有人去审计。硬件钱包存在的唯一目的——产生一个不可猜测的数字——恰恰是那个搁置在信仰之上、搁置在创始人名声之后、长达五年无人验证的东西。
直到8月7日,也就是这场抽干开始一个多星期后,公司的X公开简介仍把Coldcard描述为「最好的比特币硬件钱包,超安全」——这几个字就写在一条关于失窃的安全公告正下方。
这些年来问题来了,而且都是正确的问题。2021年7月,也就是漏洞窗口开始四个月后,一位名叫[Alex Waltz的用户在公开讨论串里问NVK](https://x.com/raw_avocado/status/1419846816770760705):公司有没有测试过真随机生成器,用的又是什么。[NVK回答](https://x.com/nvk/status/1420008199445520386)说他们没有用芯片专用的生成器,因为它太慢,他们用的是主处理器的那个,而且「_我们测试过它的熵质量_」。
被问及如何测试,他指向了固件,而Waltz读了代码,叫出了那个工具的名字:dieharder。
Dieharder衡量的是数字流看起来是否均匀——而软件回退的输出正是均匀的。它无法衡量「你以为在运行的那个生成器」是否就是钱包实际到达的那个。唯一能抓住这个问题的测试不在大纲里,而「你们怎么测的」这个问题的答案,是一个根本无法抓住它的测试。
公司直接出售了那份信任。Coldcard的营销把偏执变成了一项替顾客履行的服务:「_[我们做偏执的安全设计](https://x.com/COLDCARDwallet/status/1775557230818955504)_」,它发帖说,「_好让你能安然入睡_」。
它具体说明了顾客为什么可以。[在另一条帖子里](https://x.com/COLDCARDwallet/status/1735303894547198397)它写道,没有什么万无一失,一切都有攻击面,然后列出人们仍然安然入睡的理由:极端偏执的细节关注、可读可验证的源码、可复现构建,以及保持简单的老派工程纪律。
这些陈述每一条都在关键时刻到来之前是真的。细节关注漏掉了最重要一行里的一个字符错误。源码可读、公开,却无人阅读。构建可复现,并且忠实复现了缺陷。而那份简单,被一次120个文件的重写换掉了——用一份内部自写的库替换久经考验的代码,理由看起来更多与许可证有关,而不是与安全有关。
那几周的记录里还有一个人物,他把图景往相反方向复杂化了。Dee,一位在X上以HodlDee发帖的Coinkite支持专员,在7月30日之后的几天里公开露面,逐个回答受影响的用户。8月1日[他敦促Coldcard用户](https://x.com/HodlDee/status/2083610663981662357)立即转移可能受影响的资金,告诉他们他的私信已经堆满,但他正尽可能快地回复所有人。到8月2日,他[描述自己在处理数百封支持邮件](https://x.com/HodlDee/status/2084021222388494538)和消息,帮人们轮换密钥、迁移资金,并说明他无法控制公司的社交媒体账号。对一位说自己损失了三分之一积蓄的人,他回复说很抱歉、感同身受,如果需要任何帮助就给他发消息。对许多想弄清自己币还在不在的人来说,他成了公司的人性面孔。
别人也注意到了。「_我的心与@HodlDee同在_」,一位名叫PakoVM的评论者[在8月5日写道](https://x.com/PakoVM/status/2085003104781586929),描述了这份工作从外面看是什么样:成百上千忧心忡忡、怒火中烧的人把问题指向一名支持员工,而他花在帮助其中一人身上的每一秒,都是没花在帮助别人身上的时间。如果说有谁不该承受砸向这家公司的风暴,他总结道,那就是他。无论这份记录还显示了Coinkite如何对待审视,它也应该显示这一点。
Coinkite把验证当作信条和产品出售,而最需要验证的那件事,恰恰是任何顾客都无法检查、任何审阅者都不曾想到要去检查的。它宣传的安全措施都是真的,而它们每一个都指向了错误的地方。可复现构建证明二进制与源码一致,而源码是坏的。公开代码邀请审视,而缺陷就坐在里面,摆在明处,五年。
第十部分:更正
失窃开始后,Coinkite最初的公开解释是:这不是公司的错。最初的几个小时里,Wizardsardine的Kevin Loaec——他后来发布了那份定论性的拆解——公开指出,这个模式看起来像熵弱点:随机性没有完全坏掉,只是太弱了,而且许多被抽干的币都是老币。NVK引用转推他的帖子来反驳。不必恐慌,他写道:有人往Coldcard里装了坏种子,或者自己泄露了种子,事件牵涉大约五百个密钥。

两个判断后来都被证明是错的。种子不是用户错误,而是公司自己的;损失不是五百个密钥,而是数千个地址和一亿多美元。NVK后来删掉了那条帖子。
取而代之的不是沉默。从最初几天起,Coinkite就在沟通,而且经常沟通。7月31日它发布了一份紧急公告,并随着调查结果陆续公布而不断更新。它给仍能联系到的客户发了邮件。
8月4日它[发布了一份更长的声明](https://x.com/COLDCARDwallet/status/2084731768632991801),题为「为我们的持续调查补充公开记录」,开篇就承认了显而易见的事:「_我们理解此刻存在真实的愤怒。用户遭受了真实的损失,对受影响的人来说,任何公开声明都不够。_」这份悔意读起来是真诚的。公司没有躲藏,也没有假装损失很小。
但在一周的声明里,那些指向「失败是如何发生的、为何持续了这么久」的问题没有得到回答,而技术上的辩护最终收窄到对一个词的更正。
更正发布于8月7日,以[引用转推Jack Mallers一段广受观看的解释](https://x.com/COLDCARDwallet/status/2085541034243600805)的形式——Mallers把这次失败描述为「回退到弱熵」。并非如此,Coinkite说。不存在弱熵回退。产生可猜测种子的生成器不是Coinkite的设计,而是MicroPython的,是固件所运行平台继承来的一段通用例程。Coinkite的意图与软件回退恰恰相反:种子生成本应完全依赖硬件。把标志设为零,本意是禁用软件路径。弱生成器之所以激活,不是设计使然,而是一个链接期(link-time)错误。
那句话的每一个分句都是真的,而其中没有任何一句是开脱。
确实,Coinkite没有任何人写过一行「如果硬件失败,就用弱的东西」的代码。他们写的是一段本应让这件事不可能发生的守卫,而守卫没有触发,他们以为已经禁用的软件路径,正是钱包用了五年的那条。
你是把这个称为「蓄意的回退」还是「由链接期错误激活的继承默认值」,是一个关于措辞的问题。它不是关于「固件是谁的、谁禁用了那个标志、谁写了那个失败的守卫、谁没测试就发了货」的问题。这些问题的答案都是同一个,而更正改变不了它。
这份更正在它自己的语境里也是不完整的。代码使用的加固模式是:从硬件取一个数,用异或与一个内部伪随机生成器混合,这样即使生成器很弱,也无法败坏一次好的硬件抽取。当硬件输入悄然变成第二个软件生成器时,这个操作混合的是两个软件生成器。一个是MicroPython的——Coinkite指的那个。另一个是Coinkite自己的,在libngu内部,始于写死在源码里的常量,不贡献任何熵。这场失败里的两个生成器中,更弱的那个是Coinkite自己写的。一年前就警告过他们这段代码的James O'Beirne读到更正后,[直言不讳](https://x.com/jamesob/status/2085555839797166530):「_吹毛求疵、避重就轻。你们绝对回退到了弱熵_」,他写道,而且他们自己写的那个比他们归咎的那个更弱。
与更正同时,在那几天里,公司[还搬出了第二种解释](https://x.com/COLDCARDwallet/status/2085572285608366139),一种把手指从自己身上移开、指向时代的解释:这个bug在公开场合存在了五年,没有人找到它。是最新的AI模型才找到它的。
前半句是真的,而且正是指控的核心。后半句,公司自己的历史并不支持。2025年5月,一个没有前沿模型的人就指向了那个缺陷:一路追到那个库,指名道姓,得到的回答是「真有什么不对劲的话我们早就知道了」。披露之后,它又被人们用普通代码分析工具对准一个公开仓库再次找到。花了五年的不是「找到」,而是「去找」。
无论哪种说法,密钥和币都已经没了。一个建立在「你永远不必信任任何人」承诺之上的钱包,结果在最底层依赖着:信任一家公司、一个人,以及一份没有被人足够仔细阅读的代码。Coldcard留下的教训,正是它一直宣称要教的那个——只是它从来没有足够字面地去相信它。
不要信任。要验证。一直验证到底。
回应权(Right of Reply)
这篇调查对一家被点名公司和数个被点名个人做出了结论。发表之前,每个对象都获得了回应机会。
2026年8月5日,我向Coinkite发送了八道书面问题,涵盖本文所有不利点:种子生成路径被移到libngu而其他消费方留在硬件上的原因;公司是否有人曾通过运行时验证过种子路径实际到达哪个生成器;2020年10月switck仓库那段对话的目的;公司是否质疑switck和doc-hex账号由Peter Gray运营;是否否认James O'Beirne对其2025年5月警告的叙述;以及围绕2021年重写的许可决策。
问题通过四个渠道发出:直接私信Coinkite首席执行官Rodolfo Novak(NVK);直接私信switck账号——本报道认定其为Gray;以及邮件发给公司。doc-hex、Coldcard和Coinkite账号不接受私信。发给Novak账号的私信被打开了。一位Coinkite支持专员于8月5日确认,他已把邮件问题转交给公司团队。
我把截止日期定为2026年8月7日美东时间下午6点,并声明在此之后收到的任何回应都将作为更新发布。Coinkite没有在截止日期前回答八道问题中的任何一道。
公司在那个时期并非沉默。它就这起事件在公开场合广泛沟通,而那些声明——包括它8月7日的更正是对失败原因的说明——在本文中都被直接讨论。但针对它提出的具体问题没有得到回答。
一家面临如此规模事件的公司,可能因法律顾问的建议而无法直接回答问题,缺席回复应当考虑到这种可能性。本文不把它当作任何形式的认罪。
Coinkite、Novak或Gray在发表后收到的任何回应,都将补充进本文。
