DoubleZero 发布的软件包含中心化组件。链下私钥控制着核心智能合约,并且是运行系统中非链上必要部分的必要条件。
DoubleZero声称,美国证券交易委员会(SEC)的无异议函确认, 流向DoubleZero网络贡献者的2Z代币无需遵守《证券法》的注册要求。但这种说法似乎与实际部署的系统不符。
DoubleZero获得的美国证券交易委员会(SEC)不采取行动豁免似乎是基于与当前系统运行情况不符的陈述而获得的。实际部署的系统与DoubleZero团队向SEC和公众所作的陈述存在重大差异。
美国证券交易委员会的信函仅适用于与DoubleZero申请函中所述内容相符的系统。如果DoubleZero的实际系统与该信函不符,则不予豁免。
我们之前曾撰文讨论过 DoubleZero 的不采取行动函请求通常存在的这类问题。
现在,由于 DoubleZero 的所有软件代码都是公开的,我们将开始研究 DoubleZero 的实际实现方式与其在向美国证券交易委员会提交的无异议函请求中所作的说法不符之处。
这是 N 部分第 1 部分。DoubleZero 案很复杂,即使是 DoubleZero 向美国证券交易委员会 (SEC) 所作陈述的单个问题,也足以使 SEC 的不采取行动救济措施不适用。
哨兵报
系统架构图并未将此组件标记为“
“哨兵”:

但是,该图表中有一个标记为“DZ 资源贡献者”的部分,如果您阅读“在 DoubleZero 中发起连接请求”下的说明,您会发现以下段落:
使用 request-validator-access 命令在Solana上创建用于连接请求的帐户。DoubleZero Sentinel 代理会检测新帐户,验证其身份和签名,并在 DoubleZero 中创建访问通行证,以便服务器可以建立连接。
Sentinel 代理监视访问请求并执行它们,这与架构图左上角描述的功能相对应。
巧合的是,Sentinel 的代码可以在一个名为doublezero-offchain的 GitHub 代码库中找到,该代码库隶属于 DoubleZero 基金会,被描述为“DoubleZero 网络的链下组件”,并且应该与“DZ 资源贡献者”有关。再次强调,代码库和图表之间存在大量名称重叠。
如果你仔细查看那段代码,你会发现 Sentinel 中有一个名为“keypair”的变量:
/// Solana护照程序中授权的密钥对文件路径
/// 并将登机 DZ 分类账资金存入授权验证者的信用额度
密钥对:PathBuf,
该密钥对用于设置Solana访问权限,并启动一个名为PollingSentinel 的软件对象:
let sentinel = PollingSentinel {
dz_rpc_client: DzRpcClient::new(dz_rpc, keypair.clone(), serviceability_id),
sol_rpc_client: SolRpcClient::new(sol_rpc, keypair),
processed_cache: Arc::new(Cache::new()),
poll_interval: Duration::from_secs(15),
previous_leader_epochs: 0,
};请注意,RPC 客户端使用密钥对。RPC 代表远程过程调用(Remote Procedure Call)。这并非 Web3 独有的新技术,它已经存在一段时间了。RPC 是一种计算机调用位于其他位置的函数的方式。
在这种情况下,我们正在检查的代码调用了区块链上的函数。区块链围绕私钥构建,其核心思想是持有私钥即代表所有权或控制权。Sentinel 发起的 RPC 调用使用私钥来传递这种所有权或控制权。这遵循了针对区块链系统进行调整的标准软件工程模式。
这样做有什么目的?
顾名思义,PollingSentinel 会循环运行,不断检查新的访问请求,如下所示:
for access_id in new_requests {
let request_pda = access_id.request_pda;
match self.handle_access_request(access_id).await {
好的(_) => {
// 仅在成功处理后缓存
self.processed_cache.insert(request_pda, Instant::now(), CACHE_TTL).await;
}
Err(err) => {
错误!(?err,"验证网络访问请求时遇到错误;将在下次轮询时重试");
// 不要缓存失败信息 - 允许在下一个轮询周期重试
}
}
}我们提取了一些具有代表性的代码片段来说明其工作原理。
这意味着要从函数中间提取代码块。如果没有合适的工具,Rust 中的一些设置代码可能难以解析,因此我们专注于核心逻辑。
PollingSentinel 监视访问请求,并使用命令行传递的私钥通过handle_access_request来处理请求,顾名思义,它执行的是处理访问请求的操作。
即使您看不懂代码,也可以查看注释和日志消息:
async fn handle_access_request(&self, access_id: AccessId) -> Result<()> {
let service_key = match &access_id.mode {
AccessMode::SolanaValidator(a) => a.service_key,
AccessMode::SolanaValidatorWithBackupIds { attestation, .. } => attestation.service_key,
};
info!(%service_key, request_pda = %access_id.request_pda, "正在处理访问请求");
let validator_ips = self.verify_qualifiers(&access_id.mode).await?;
如果 !validator_ips.is_empty() {
// 为所有验证器(主验证器 + 备用验证器)颁发访问通行证
for (validator_id, validator_ip) in validator_ips {
rpc_with_retry(
|| async {
self.dz_rpc_client
.issue_access_pass(&service_key, &validator_ip, &validator_id)
等待
},
"issue_access_pass",
)
等待?
info!(%validator_id, %validator_ip, user = %service_key, "已颁发访问通行证");
}
let signature = rpc_with_retry(
|| async {
self.sol_rpc_client
.grant_access(&access_id.request_pda, &access_id.rent_beneficiary_key)
等待
},
"grant_access",
)
等待?
info!(%signature, user = %service_key, "访问请求已批准");
metrics::counter!("doublezero_sentinel_access_granted").increment(1);
} 别的 {
let signature = rpc_with_retry(
|| async {
self.sol_rpc_client
.deny_access(&access_id.request_pda)
等待
},
"拒绝访问",
)
等待?
info!(%signature, user = %service_key, "访问请求被拒绝");
metrics::counter!("doublezero_sentinel_access_denied").increment(1);
}
好的(())
}需要注意的是,“处理访问请求”包含大量逻辑,而这些逻辑总是涉及大量的 RPC 调用。这些调用是指向位于Solana或 DoubleZero 区块链上的函数进行的。
是的,没错,DoubleZero 运行在Solana和他们自己的 DoubleZero Ledger( Solana的一个分支)的混合体上,再加上链下依赖项。
该函数中对 dz_rpc_client(DZ 链)和 sol_rpc_client( Solana)的调用均由授予 Passport 程序访问权限的私钥签名。因此,当此代码调用 grant_access() 和 issue_access_pass() 以及类似函数时,所有这些都取决于运行 Sentinel 的用户是否拥有正确的私钥。此过程由私钥控制,Ergo是中心化的。没有这些特定的私钥,Sentinel 无法运行。
还记得上面提到的“在Solana 的Passport 程序中授权的密钥对文件”吗?这意味着某个地方存在一个 Passport 程序,它也知道这些特殊密钥。事实上确实如此。如果我们查看一下,就能在 Passport 程序端看到这个中央控制机制。以下是 Passport 程序中处理访问请求的代码片段。这是上面一些 RPC 调用的另一端。
// 账户 0 必须是程序配置。
// 账户 1 必须是 DoubleZero 账本哨兵。
//
// 此调用确保 DoubleZero Ledger 哨兵是签名者并且是
// 与程序配置中编码的哨兵相同。
let authorized_use =
VerifiedProgramAuthority::try_next_accounts(&mut accounts_iter, Authority::Sentinel)?;
这段代码确保 Sentinel(即上文所述的链下组件)参与了请求的发起。此外,它还会检查 Sentinel 是否“已编码到 [passport] 程序配置中”。这与开放、无需许可的系统理念不符。该代码包含一系列互锁检查,以确保使用特定的私钥发起对一个自称开放且无需许可的网络的访问请求。这些私钥对注册流程拥有独占控制权。
与其查看额外的代码,我们可以直接参考 DoubleZero 自己的文档,文档中对此有明确的说明:
服务密钥必须由贡献者生成并安全存储,然后才能提交给 DoubleZero 基金会进行授权。这确保所有 CLI 交互都可以进行加密验证,并与正确的贡献者帐户关联。[此处]
“如何在 IBRL 模式下连接到 DoubleZero — 对于 RPC 节点”的第 2 步指出:
2. 联系 DoubleZero 基金会
DoubleZero基金会。您需要提供您的DoubleZeroID、验证器ID(节点ID)以及您将用于连接的公网IPv4地址。
文档明确指出,用户必须联系 DoubleZero 基金会才能完成连接过程。这一要求与无需许可的系统理念相悖。
即使 DoubleZero 的既定政策是“不对此过程行使自由裁量权”,基金会仍然保留着对这一过程的控制权。
文档和代码共同表明:
- 在与Solana上的 passport 程序进行特权交互的代码中,直接引用了“在 Solana 上的 passport 程序中授权的密钥对文件”。
- 基金会明确声明其进行“授权”。
- 要求用户向基金会“提供您的 DoubleZeroID、您的验证器 ID(节点 ID)以及您将用于连接的公共 IPv4 地址”。
单凭文档就能清楚地说明这一点,无论一个人是否具备解读代码的能力。
该团队向美国证券交易委员会提交的陈述表明他们理解这些问题。
不采取行动函
提交给美国证券交易委员会(SEC)的请求不采取行动的信函包含以下段落:
总而言之,网络提供商是该系统中成熟且独立的运营商。他们负责将自身链路与网络集成,安装相关的FPGA设备,设定并达到服务级别,维护链路,并最终根据需要断开与网络的连接。所有网络提供商均非基金会的附属机构,基金会及其他任何捐助者均不代表他们执行这些工作(除了提供培训和协调网络提供商之间的工作之外)。
该文档还包含对该段落的脚注:
基金会在协助网络供应商将新的光纤链路物理连接到网络方面发挥着有限的作用。基金会不从这些服务中获利,并且在项目启动初期,不会对网络供应商的接纳或拒绝行使任何自主权。基金会计划随着时间的推移,最终实现完全无需许可的流程。
脚注声明明确限定于物理连接方面的援助,并且仅涉及物理基础设施方面的援助。
即使将“物理”的含义广义地解释为包括逻辑连接,这种表示方法仍然与实际部署的系统不一致。
前一节规定,基金会必须采取某些措施,才能使任何人将其链接整合到网络中。
要使系统正常运行,必须有人使用一组特定的私钥来运行 Sentinel 程序。是否存在其他中心化措施将在后续分析中探讨。
我们暂且搁置这个问题吧。
证据表明,该基金会是新用户注册流程中某些关键步骤的唯一提供者。申请不采取行动的声明表明,该基金会并非关键系统功能的核心提供者。该信函特别排除了物理连接方面的协助,并指出最终目标是使此类协助无需许可。
如果此事诉诸法律,可能会出现这样的问题:是否应该基于有关未来系统变更的陈述而给予不采取行动的救济。
应根据系统的实际部署情况,而不是既定的未来意图,来决定是否适用监管要求。
有人可能会将上述行为描述为“网络提供商之间的协调”,但这种协调是通过由所有者的私钥控制的合约来实现的。
如果这种形式的“协调”符合去中心化的定义,那么从监管角度来看,集中式系统和去中心化系统之间的区别就变得毫无意义了。
代码在哪里?
值得注意的是,没有指向代码和链上事件的区块浏览器链接。这是因为,据我们所知,该代码尚未在Solana上进行验证,也没有公开的部署地址列表。

该代码未在公共浏览器上进行验证。文档中未包含合约地址,也没有按照惯例提供部署工件的存储库位置。此外,该代码是可升级的。
doublezero-solana 代码库中已将合约地址硬编码到代码中,因此我们知道:
- 收益分配:dzrevZC94tBLwuHw1dyynZxaXTWyp7yocsinyEVPtt4
- 护照:dzpt2dM8g9qsLxpdddnVvKfjkCLVXd82jrrQVJigCPV
两者都未经证实,而且都可以升级,但这些信息并不容易找到。
标准做法包括签订附有清晰地址记录的已核实合同。虽然可以通过查阅法规获取这些信息,但缺乏标准化的文档记录做法仍然值得关注。
讨论
分析表明,已授予的不采取行动豁免可能不适用于已部署的 DoubleZero。
更多背景信息很重要,因为DoubleZero的请求促使美国证券交易委员会委员皮尔斯(Peirce)这样描述美国证券交易委员会在这一切中的角色:
我们的工作是真诚地与创新者接触,认真倾听他们解释其模式的运作方式,并深思熟虑、精准地运用我们的法定职责。
她还把该项目描述为“根据网络规则,以编程方式向参与网络的用户分发代币”,这在某种程度上是正确的。
然而,鉴于基金会对分配过程的控制,其集中化程度似乎与传统的集中式系统类似。
皮尔斯的声明似乎接受了 DoubleZero 将自身描述为“开放且分布式的点对点网络”的说法,而没有考虑到上面提到的集中式控制机制。
请求不采取行动的救济措施似乎与实际部署的 DoubleZero 系统存在实质性差异,但在美国证券交易委员会批准救济措施后,DoubleZero 广泛宣传了这一结果。
如果向美国证券交易委员会提交的陈述与已部署的系统不一致,这就引发了人们对不采取行动程序的质疑,以及委员会是否应该进一步澄清。
我们知道美国证券交易委员会的职责是信息披露,而不是审查。
我们并不期望美国证券交易委员会审查此类不采取行动请求是否与相关系统相符。这并非美国证券交易委员会的职责,而且不采取行动函中也正确地包含了以下免责声明:
此立场基于您在信中向本部门所作的陈述。任何不同的事实或情况都可能导致本部门得出不同的结论。
美国证券交易委员会基于披露的方法并不排除处理基于重大不准确陈述而可能获得不采取行动豁免的情况。
DoubleZero 在其促销活动中继续依赖不采取行动豁免条款。任何未来的执法行动都可能涉及不采取行动豁免条款是否适用于已部署的系统这一问题。
当前形势持续的时间越长,对不采取行动救济措施的依赖就越根深蒂固。
代码是公开的,可以进行分析。可以发现不采取行动请求与已部署系统之间存在的实质性差异。
如果系统与申请函中的描述存在差异,则该项救济措施可能不适用。团队对救济措施适用性的主观判断与该措施是否适用这一法律问题截然不同。
重要的是,团队可以拿出一份不适用于他们项目的不采取行动函,并提出一些普通人无法分辨的明显错误的说法,因为他们缺乏做出这种判断的技能。
监管行动应基于彻底调查,而不是第三方分析。
委员会就事实与陈述不符时无异议函的适用性作出公开澄清将非常有价值。
明确在不采取行动请求中,何时需要对重大不实陈述进行更严格的审查,将有助于划定更清晰的界限。若缺乏此类明确规定,不采取行动程序在确认合规性方面的效用可能会受到影响。

DoubleZero 是中心化的,并且误导了美国证券交易委员会:N 的第一部分最初发表在 Medium 上的ChainArgos上,人们正在那里通过突出显示和回应这篇文章来继续讨论。




