# 委托

> 哈希、域运算和曲线运算都有专用电路。哪些 SDK 调用会用到它们、成本多少、对操作数的规则，以及把库代码引向它们的 vendored crate。

有些计算，用专门为它们构建的电路来证明，要比作为一串 RISC-V 指令来证明便宜得多。远地虚拟机把这些称为**委托**。委托是一个电路族，由 `ecall` 调用，证明作用于 RAM 中一个由字组成的帧的某个函数；客户程序（guest）SDK 在普通函数背后替你发出这些调用。你永远不必自己写 `ecall`。

## 你调用什么，会用到哪个委托

| 你调用的 | 委托 | 一次调用证明的内容 |
| --- | --- | --- |
| `guest_sdk::keccak256(&[u8]) -> [u8; 32]` | `KECCAK_F` | keccak-f[1600] 的一轮；一次置换是 24 次调用，海绵结构和填充由客户程序代码完成 |
| `guest_sdk::sha256(&[u8]) -> [u8; 32]` | `SHA256_COMP` | 压缩函数的四轮；一次压缩是 16 次调用 |
| `guest_sdk::ec_add`、`ec_mul`、`ec_identity` | `EC_ADD` | secp256k1 或 BN254 G1 上一次完全点加的三分之一 |
| `guest_sdk::poseidon2_permute(&mut [u8; 96])` | `POSEIDON2` | `Fr` 上一次宽度为 3 的 Poseidon2 置换 |
| `field::Fr` 的加法、乘法、求逆 | `FR_ARITH` | 一次 `Fr` 运算，在客户程序目标上发生，无需显式指名任何东西 |
| `transcript::poseidon2_permute` | `POSEIDON2` | 同一个置换，经由 transcript crate |
| 作用于 `ModMulFrame` 的 `guest_sdk::recursion::mod_mul` | `MOD_MUL` | 一次 256 位的 `a·b mod m`，`m` 是四个以太坊模数之一 |

这些函数的结果与其软件定义逐位一致。`keccak256` 是以太坊的 Keccak，而不是 SHA3-256。`sha256` 遵循 FIPS 180-4。`ec_add` 使用 Renes、Costello 和 Batina 的完全加法公式（2015，算法 7），因此倍点、`P + (−P)`、单位元以及任意 `Z` 都不需要特殊处理。

```rust title="在客户程序中调用哈希与曲线运算"
use guest_sdk::{ec_mul, keccak256, recursion::SECP256K1_GROUPS, ProjectivePoint};

let digest: [u8; 32] = keccak256(b"blockchain-native");

// A point is homogeneous projective (x = X/Z, y = Y/Z), each coordinate eight
// little-endian u32 limbs below the field modulus. The scalar is eight limbs too.
fn times(p: &ProjectivePoint, k: &[u32; 8]) -> ProjectivePoint {
    ec_mul(&SECP256K1_GROUPS, p, k).expect("EC_ADD is implemented on Apogee")
}
```

## 库代码同样能用上委托

客户程序工作空间给三个 crate 打了补丁，使其中的代码在客户程序目标上调用委托，并以上游代码作为回退路径：

| Crate | 版本 | 用到的委托 |
| --- | --- | --- |
| `k256` | 0.13.4 | 域元素乘法和标量乘法用到 `MOD_MUL`；`ProjectivePoint` 的加法、混合加法和倍点用到 `EC_ADD` |
| `ark-ff` | 0.6.0 | BN254 两个域上的 Montgomery 乘法和平方用到 `MOD_MUL` |
| `revm-precompile` | 43.0.2 | 预编译合约 `0x02` 用到 `SHA256_COMP`；`0x06` 和 `0x07` 用到 `EC_ADD` |

依赖这些 crate 的客户程序，会通过 `guests/Cargo.toml` 的 `[patch.crates-io]` 自动得到打过补丁的副本。不打补丁时，仅 `k256` 的域乘法和平方就占了一个主网区块 44% 的周期。secp256k1 签名恢复是普通的 `k256` 代码，补丁把它变成了委托运算。

## 委托的成本

只有当程序链接了某个委托电路族的某个 shim 时，该电路族才成为程序的一部分；调用的成本以该电路族高度的分片计：

- **链接了但从未调用：没有成本。** 该电路族已声明，证明零个分片。
- **调用一次：一整个分片。** 不论占用率如何，一个分片的成本都按其完整高度计算。
- **大量调用：每次调用的成本很低。** 分片高度增长时，其证明只按每个变量增加一轮求和校验（sumcheck）。

| 电路族 | 高度 | 工作单元 | 每单元调用次数 | 每分片单元数 |
| --- | --- | --- | --- | --- |
| `KECCAK_F` | `2^18` | keccak-f[1600] | 24 | 10,922 |
| `SHA256_COMP` | `2^18` | 一次压缩 | 16 | 16,384 |
| `EC_ADD` | `2^16` | 一次完全点加 | 3 | 21,845 |
| `MOD_MUL` | `2^16` | 一次 `a·b mod m` | 1 | 65,536 |
| `POSEIDON2` | `2^8` | 一次置换 | 1 | 256 |
| `FR_ARITH` | `2^8` | 一次 `Fr` 运算 | 1 | 256 |

代价主要在内存而不是时间：一个 `2^18` 的 `KECCAK_F` 分片在前向过程中约占用 42 GiB 的域元素，实测以太坊区块的内存峰值，就是由两个同时处理中的此类分片决定的。

## 操作数规则

- **操作数必须小于其模数。** `MOD_MUL` 或 `EC_ADD` 的操作数若大于或等于其选择子所指定的模数，就没有证明：执行器会以致命错误 `DelegationFrame` 拒绝该帧。vendored 的 `k256` 在调用之前，会先对它惰性约简的域元素做约简。
- **点不会替你检查。** `EC_ADD` 证明的是公式的算术。一个点是否在曲线上，是调用代码要回答的问题；从证明者提示（advice）中取点的客户程序必须检查这一点。
- **每个多次调用的操作都对应一个 SDK 函数。** 一次 keccak 置换是在同一个帧上的 24 次调用，一次 SHA-256 压缩是 16 次，一次点加是 3 次。每次调用只证明自己的那一步，顺序错了也不会被拒绝：它只会算出别的东西。使用会按顺序发出调用的 `keccak256`、`sha256` 和 `ec_add`，而不是原始的 shim。
- **退出状态 72** 表示某个委托返回了其 shim 拒绝接受的结果。在远地虚拟机自己的执行器上，格式正确的帧不会出现这种情况。

## 哪些运算没有委托

- 任意模数的 EVM `MULMOD`、`MODEXP`、BLS12-381，以及上表之外的所有原语，都以普通指令运行。
- 没有任何签名方案或配对作为整体被委托。secp256k1 签名恢复是建立在 `MOD_MUL` 和 `EC_ADD` 之上的 `k256`；BN254 配对是建立在 `MOD_MUL` 之上的 `ark-bn254`。
- 委托承担的是运算的核心。填充、海绵结构、分块循环以及标量乘法的阶梯（ladder），都是客户程序代码，以指令的形式被证明。

面向客户程序的签名方案在 [v2.0.0 路线图](https://apogee.gweb3networks.com/docs/quantum-leap/signatures)上。

## 新增一个委托值得吗？

周期分析器在每份报告中都会为显而易见的候选项定价，给出一个委托最多能省掉多少周期的上限：

```text
removable = max(0, cycles − calls·(4 + 2·frame_words))
```

`cycles` 是该类别在这次运行中所占的周期，`calls` 是进入候选函数的次数，`4 + 2·frame_words` 是委托之后仍会留下的 shim：帧的存储、`ecall` 以及结果的加载。它没有计入新电路族分片的成本，所以只能当作上界。[运行与性能分析](https://apogee.gweb3networks.com/docs/launch/run#profiler)给出了一份报告。

每个委托逐列展开的规范，见[委托电路](https://apogee.gweb3networks.com/docs/auditors/spec/delegation-circuits)。
