nautilus_trader Derive 适配器:EIP-712 自托管签名管线的第三方材料引用与字节级等价性验证
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
本篇技术指南聚焦于 nautilus_trader 的 Derive 适配器如何以"第三方材料"的方式引用 Derive.xyz 官方 Python 签名 SDK(v2-action-signing-python),并以该 SDK 为行为基准构建 Rust 端 EIP-712 签名管线的字节级等价性测试(oracle 向量验证)。读完本文,你将掌握这份第三方许可声明的完整含义、oracle 向量夹具(fixture)的生成与重放机制、签名管线的底层实现细节,以及未来上游版本升级时的"再固定 + 再生成"流程。
一、文档定位:一份"有技术内涵"的第三方许可证声明
仓库中的 THIRD_PARTY_LICENSES.md 虽然以许可证声明命名,但它的技术分量远超普通合规文件。它回答了一个关键工程问题:nautilus_trader 的 Rust 版 Derive 自托管签名协议实现,其行为正确性以什么为基准来验证?
答案是该文档与源码共同揭示的:
- Rust 端
src/signing/目录下的 EIP-712 签名管线是原始实现(original implementation),并非对上游代码的移植或封装; - 上游 Python SDK 仅作为行为参考(behavioral reference),用于生成 oracle 向量并记录在 signing_trade_action_vectors.json 中;
- 向量由 generate_oracle.py 生成,向量元数据中记录固定的上游修订版本。
这种"独立实现 + 官方参考实现做等价性对拍"的做法,让适配器在完全自研的同时,又能与 Derive 官方生态保持字节级一致。
二、第三方材料清单(原文档核心内容)
原文档声明的第三方材料如下,这是整个验证体系的依据:
| 项目 | 值 |
|---|---|
| 上游项目 | Derive.xyz -v2-action-signing-python |
| 用途 | 作为 Rust EIP-712 签名管线的行为参考,生成 oracle 向量 |
| 固定修订版本 | d1914d61985e33559244da242892c7255b6fd0ca |
| 固定版本号 | 0.0.13(2025-08-21 提交) |
| 归属(作者) | Derive.xyz<joshua@derive.xyz>、8baller<8baller@station.codes>(声明于上游pyproject.toml) |
| 许可证 | MIT(经 pyproject classifier 声明;固定修订版本的上游仓库未附带 LICENSE 文件) |
| 向量生成脚本 | generate_oracle.py |
| 向量输出文件 | signing_trade_action_vectors.json |
两点需要特别留意:
- 许可证信息以 pyproject classifier 为准:上游仓库在固定修订版本上没有 LICENSE 文件,因此 MIT 判定来自
pyproject.toml中的声明。这一细节同时被写进了 oracle 向量的元数据(见下文)。 - 上游来源:项目地址为
github.com/derivexyz/v2-action-signing-python(仓库源码中 signing/mod.rs 的文档注释亦注明了该来源)。
三、为什么需要第三方参考:Derive 自托管签名协议
Derive 是构建在 Derive Chain 上的去中心化衍生品交易协议。其安全模型中,每一个改变状态的请求都必须携带一个 EIP-712 typed-data 签名,由会话密钥(session key)对 secp256k1 私钥签名,并由智能合约钱包验证。这意味着交易、改单、撤单等动作的签名格式是协议级约定,容不得半点字节偏差——任何编码顺序、填充方式或哈希组合的错误,都会导致合约端验签失败或签名被拒绝。
这正是引入官方 SDK 作为 oracle 的原因:Rust 实现必须与官方 Python 实现产生完全相同的字节序列,才能确信协议兼容。
协议常量
协议常量来自 Derive 官方 Protocol Constants 参考文档,并固化在 consts.rs 中:
- Domain separator:mainnet 为
0xd96e5f90...c60441b,testnet 为0x9bcf4dc0...e3dd1105; - Action typehash:
0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17(跨网络一致); - Trade module 合约地址:mainnet
0xB8D20c2B...5b5e7b,testnet0x87F28638...3f2be; - 最小签名 TTL:
MIN_SIGNATURE_TTL = 5 分钟(v2 文档要求signature_expiry_sec至少在未来 5 分钟); - 触发单签名 TTL:
TRIGGER_ORDER_SIGNATURE_TTL = 31 天(mainnet 要求触发单的过期时间在 30~90 天区间,一天缓冲规避时钟漂移); - 小数定标:所有链上十进制字段以 1e18 定点整数表示(
DECIMAL_SCALE)。
这些常量既用于生产配置的默认值,也作为 oracle 测试的比对基准——见下文第五节。
四、oracle 向量:字节级等价性的证据链
4.1 向量夹具的结构
signing_trade_action_vectors.json 是验证的核心夹具,结构为metadata + vectors:
- metadata记录了来源(
github.com/derivexyz/v2-action-signing-python)、上游版本0.0.13、固定修订版本d1914d...、生成脚本路径、许可证声明(MIT,含"上游无 LICENSE 文件"的说明)以及一条重要注释:签名使用 RFC 6979 确定性随机数,因此每个值都是字节级相等目标(byte-equality target); - vectors是 5 组测试向量(对应 5 个行为分支,见下节)。
每组向量完整记录了从输入到签名的全链路中间值:
domain_separator / action_typehash / module_address / subaccount_id nonce / signature_expiry_sec / owner / session_key / signer trade(asset_address、sub_id、limit_price、amount、max_fee、recipient_id、is_bid) module_data(ABI 编码后的完整字节) module_data_hash / action_hash / typed_data_hash / signature以第一组向量limit_buy_round_mainnet为例(节选):
{ "case": "limit_buy_round_mainnet", "environment": "mainnet", "domain_separator": "0xd96e5f90797da7ec8dc4e276260c7f3f87fedf68775fbe1ef116e996fc60441b", "action_typehash": "0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17", "module_address": "0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b", "subaccount_id": 30769, "nonce": 1695836058725001, "signature_expiry_sec": 2147483647, "trade": { "asset_address": "0x000000000000000000000000000000000000abcd", "sub_id": "42", "limit_price": "100", "amount": "1", "max_fee": "1000", "recipient_id": 30769, "is_bid": true }, "module_data_hash": "0xc9adef7e1b0648c010e846ee4a30ad72a3320279ab75b986e296dd9b9cb39c10", "signature": "0x9f28ed4e5b014e5214143d4060ea7758e4c8f837a3287841909b046caf43a6fd18a7707f30241f6fafff7ba53f839381390e13354264d984ea648de6c20369d31c" }注意向量中的会话密钥0x2ae8be44...a4816bbd和所有者地址0x8772185a...5d698来自上游 SDK 自带的测试套件,不控制任何真实资金。
4.2 五个行为分支的覆盖设计
generate_oracle.py 中的CASES数组精心选择了 5 组输入,覆盖交易编码器和 action-hash 组合的各个行为分支:
| 用例 | 覆盖点 |
|---|---|
limit_buy_round_mainnet | mainnet 环境、整数价格、买单(bid) |
limit_sell_fractional_testnet | testnet 环境、小数价格与数量(3500.01 / 1.25)、卖单 |
sell_negative_amount_testnet | 负数 amount(-0.75)的 ABI 编码(int256 补码) |
option_buy_large_sub_id_mainnet | 超出 64 位范围的期权 sub_id(2^95 量级)的 uint256 编码 |
limit_buy_zero_max_fee_testnet | max_fee为零的边界情形 |
由于private/order、private/trigger_order、private/replace都通过同一条 EIP-712 管线对TradeModuleData签名,一组 trade 模块向量即可覆盖全部签名面(signing surface)。
4.3 为什么字节相等是有效的验证
oracle 成立的关键前提是RFC 6979 确定性随机数:相同输入在任何实现、任何时间运行都产生相同签名。这意味着 Rust 实现与 Python SDK 对相同输入产出的module_data、module_data_hash、action_hash、typed_data_hash乃至最终 65 字节签名,都必须与夹具逐字节相等(byte equality),而不是"语义等价"。
五、Rust 签名管线的源码级实现
5.1 三步哈希管线
signing/mod.rs 的模块文档给出了完整管线:
action_hash = keccak256(abi.encode( [bytes32, uint, uint, address, bytes32, uint, address, address], [ACTION_TYPEHASH, subaccount_id, nonce, module_address, keccak256(module_data_abi_encoded), signature_expiry_sec, owner, signer], )) typed_data_hash = keccak256(0x1901 || DOMAIN_SEPARATOR || action_hash) signature = secp256k1_sign(typed_data_hash, signer_key)对应实现分布在 eip712.rs 中:
compute_action_hash:将 8 字段 ABI 元组(typehash、subaccount、nonce、module、module_data_hash、expiry、owner、signer)编码后取 keccak256。元组字段的顺序是承载字节等价性的协议约定——测试test_compute_action_hash_pins_byte_layout用固定期望值0x509b526a...锁死布局,任何字段交换、删除或重排都会立刻被检测;compute_typed_data_hash:拼接0x19 0x01前缀 + domain separator + action_hash 后取 keccak256,测试test_compute_typed_data_hash_includes_19_01_prefix专门验证前缀参与哈希;SignedAction::sign:先校验过期时间(见 5.3),再走完"模块编码 → 模块哈希 → action 哈希 → typed-data 哈希 → secp256k1 签名"全流程,签名结果以 65 字节原始形式保存,并通过signature_hex()输出0x前缀的 130 字符十六进制串。
5.2 Trade 模块的 ABI 编码
modules/trade.rs 实现TradeModuleData,其 ABI 元组为(address, uint256, int256, int256, uint256, uint256, bool),对应(asset_address, sub_id, limit_price, amount, max_fee, recipient_id, is_bid):
- 全部为静态类型,编码结果是7 个 32 字节字(测试
test_encode_produces_seven_static_words断言长度恰为 224 字节); limit_price与amount在 ABI 层是有符号 int256(即使价格通常非负),max_fee是无符号 uint256 且拒绝负数;- 十进制字段乘以
DECIMAL_SCALE(1e18)转为定点整数;负数amount编码为 int256 补码(测试test_encode_negative_amount_is_two_complement断言符号扩展字节为0xff); - 地址左填充 12 字节零、
sub_id大端 uint256、is_bid布尔打包为0x01/0x00,均有对应单元测试逐一锁定; - 编码指纹测试
test_keccak_of_encoded_payload_is_stable将编码结果的 keccak256 锁死为0xc9adef7e...9cb39c10——这恰好与 oracle 向量中的module_data_hash一致,形成测试与夹具的交叉印证。
5.3 过期时间与随机数管理
签名安全还依赖两个支撑模块:
- 过期校验:
SignedAction::sign会先以MIN_SIGNATURE_TTL(5 分钟)校验signature_expiry_sec,不足则返回TypedDataError::ExpiryTooSoon(测试test_sign_rejects_expiry_that_is_too_soon以 1 分钟过期验证拒绝路径);系统时钟早于 UNIX 纪元则返回ClockBeforeEpoch。 - nonce 管理:nonce.rs 实现进程级
(wallet, subaccount)随机数分配器。Derive 的 venue schema 定义唯一 nonce 为 UTC 毫秒 + 至多 3 位后缀,分配器采用utc_ms * 1000 + suffix格式,保证:同毫秒内单调递增、时钟回拨时在最后逻辑毫秒上继续推进(而非倒退)、按(wallet, subaccount)独立跟踪(钱包地址统一转小写,避免 checksum 形式产生重复 nonce)、DashMap分片 +compare_exchange循环保证并发安全。
5.4 签名上下文解析
signing/context.rs 的resolve_signing_context将凭据(钱包地址、会话密钥、子账户 ID)与 DeriveExecutionClientConfig 结合,解析出完整的SigningContext:domain separator、typehash、trade module 地址等协议常量优先取配置覆盖值,未配置时回落为consts.rs中的环境默认值;max_fee_per_contract为必填项。
六、等价性测试如何在 Rust 中重放
oracle 测试直接内嵌在 eip712.rs 的#[cfg(test)]模块中,通过include_str!("../../test_data/common/signing_trade_action_vectors.json")将夹具编译进测试二进制。核心测试链包括:
test_oracle_fixture_records_upstream_provenance:仅强制校验元数据四要素(source、version、revision、generator)非空——刻意不锁定具体值,这样未来换用新上游版本重新生成夹具时无需改动测试结构;test_oracle_vectors_use_production_protocol_constants:逐一断言夹具中每个向量的 domain separator、typehash、module 地址与common::consts中生产的常量一致,防止"夹具自洽但脱离线上配置"的陷阱;test_signing_matches_upstream_sdk_oracle_vectors:真正的对拍测试——对每个向量,用 Rust 实现重新编码module_data、计算module_data_hash、action_hash、typed_data_hash并签名,逐字节断言与上游夹具相等。
此外还有签名可恢复性测试(test_sign_produces_recoverable_signature,从 65 字节签名恢复地址并与会话密钥比对,模拟合约端反向验签)和签名在 Debug 输出中脱敏(REDACTED)测试。
七、版本固定策略与未来升级路径
固定修订版本是 oracle 有效性的前提。当前钉在d1914d61985e33559244da242892c7255b6fd0ca(0.0.13)。文档明确给出了升级流程:
1. 更换上游版本(例如未来的 V3 signer):重新固定修订版本 2. 重新运行 generate_oracle.py 生成新夹具(元数据记录新 revision) 3. Rust 测试结构保持不变也就是说,"换版本"只是一次re-pin + 再生成,不涉及 Rust 测试逻辑的任何改动——这正是元数据校验测试刻意不锁定具体版本值的原因。
重新生成夹具的操作步骤(记录在 generate_oracle.py 的模块文档中):
git clone https://github.com/derivexyz/v2-action-signing-python cd v2-action-signing-python git checkout <upstream_revision> # UPSTREAM_REVISION in this script python3 -m venv .venv .venv/bin/pip install . .venv/bin/python <nautilus_trader 仓库根目录>/crates/adapters/derive/tests/oracle-py/generate_oracle.py生成器按自身所在位置解析默认输出路径,因此无论从哪个工作目录运行,都会把夹具写入test_data/common/signing_trade_action_vectors.json(也可用--out参数覆盖)。
值得注意的工程细节:生成器在记录 SDK 私有计算的action_hash、typed_data_hash时,会独立重算typed-data hash(keccak256(0x1901 || domain || action_hash))并与之比对,不一致即报错——这为夹具本身提供了防篡改护栏,避免"以错误实现为基准"。
八、许可证合规要点
从合规角度看,该声明明确了:
- 上游
v2-action-signing-python以MIT许可证使用(依据 pyproject classifier 声明); - 由于固定修订版本上游仓库未附带 LICENSE 文件,归属与许可信息需以官方元数据为准;
- 归属作者为 Derive.xyz(joshua@derive.xyz)与 8baller(8baller@station.codes)。
这一信息同时被冗余记录在 oracle 夹具的metadata.license字段中,形成文档与数据的双重留痕,便于审计与追溯。
九、相关文件索引
- 第三方许可证声明:crates/adapters/derive/licenses/THIRD_PARTY_LICENSES.md
- 签名模块总览:crates/adapters/derive/src/signing/mod.rs
- EIP-712 实现与 oracle 测试:crates/adapters/derive/src/signing/eip712.rs
- Trade 模块 ABI 编码器:crates/adapters/derive/src/signing/modules/trade.rs
- Nonce 分配器:crates/adapters/derive/src/signing/nonce.rs
- 签名上下文解析:crates/adapters/derive/src/signing/context.rs
- 协议常量:crates/adapters/derive/src/common/consts.rs
- 适配器配置:crates/adapters/derive/src/config.rs
- Oracle 生成器:crates/adapters/derive/tests/oracle-py/generate_oracle.py
- Oracle 向量夹具:crates/adapters/derive/test_data/common/signing_trade_action_vectors.json
- Derive 适配器集成测试:crates/adapters/derive/tests/integration/
十、小结
这份第三方许可证声明揭示的,其实是 nautilus_trader Derive 适配器在加密签名领域的一种高置信度工程实践:独立实现协议、以官方 SDK 生成字节级 oracle、在 CI 测试中逐字节对拍、以固定版本保证可复现。它既满足了第三方材料的归属与许可合规要求,又为自托管签名这种"错一个字节就失败"的协议提供了可验证的正确性保证。理解这套机制,无论是审查适配器代码、排查签名问题,还是规划上游升级,都能事半功倍。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考