# 委托

> 昂贵的函数如何在不扩大指令电路的前提下拥有自己的电路。调用方式、让每个请求恰好与一次调用配对的锚点、六个电路，以及它们的经济性。

哈希和大整数运算在真实工作负载中占主导：在一个以太坊主网区块中，被委托之前，仅 secp256k1 的域乘法和平方就占了 44% 的周期。逐条指令地证明它们是可行的，但很慢。**委托**为这样的函数提供一个专属的电路族，由客户程序（guest）调用，这样指令电路保持精简，程序也只为它调用的委托付费。

## 调用方式

委托只会被调用，从不被解码。客户程序在 RAM 中写入一个由 32 位字组成的帧，然后发出 `ecall`，`a7` 中放委托编号，`a0` 中放帧的基地址。这个 `ecall` 是 `ADD_SUB_LUI_AUIPC` 电路族中的一行，即**请求**。实际的计算是该委托自身电路族中的一行，即**调用**：它在发起请求的那个周期读取帧中的每个字，并把每个字（其中包括结果）写回。调用不拥有任何周期，它搭乘发起它的那个周期。

帧按字对齐，并完全位于普通 RAM 中，所以任何帧都不会与公开窗口或证明者提示（advice）区域重叠，帧的读写也都是普通的内存查询。因此，委托计算出的结果与任何一次存储一样受到绑定：经由那唯一的内存多重集。

## 锚点

请求与调用必须一一配对：否则多个请求可能对着同一次调用闭合，使一些调用实际上没有执行；或者一次未被请求的调用可能改写某个帧。它们通过同一个内存多重集配对，配对发生在一个**锚点空间**中，这个空间只属于该委托类型，任何指令都无法触及：

| | 读 | 写 |
| --- | --- | --- |
| 请求，周期 `c` | `T(s, base, 0, 0)` | `T(s, base, 4c + 3, v)` |
| 调用 | `T(s, base, 4c + 3, v′)` | `T(s, base, 0, 0)` |

请求一侧的三个门把它的读取固定在时间戳 0、值 0，并使它向 `a0` 写入 0。于是，时间戳为 0 的元组恰好就是各请求的读取和各调用的应答，所以在相同的基地址上，调用与请求一样多；又因为没有两个请求共享同一个周期，每次调用的读取恰好就是某一个请求的写入。每次调用都位于其请求的基地址和周期上。委托电路中的任何门都完全不必知道请求的存在。

## 多次调用，一个操作

一行容纳不下的操作，就在同一个帧上分成几次调用，由帧中的一个字指明是哪一步：一次 keccak-f[1600] 置换是 24 次轮调用，一次 SHA-256 压缩是 16 次各含四轮的调用，一次完全点加是三次调用。没有任何门把两行连接起来。每次调用都在帧的当前状态上证明自己那一步；它的读取位于每个字唯一的那条内存历史上，所以读到的是上一步的写入。每一步都按顺序执行，这要由调用代码来保证，而调用代码是以指令形式被证明的客户程序代码。SDK 用一个函数发出每个多次调用的操作，所以客户程序从不需要手动安排步骤的顺序。

## 静态声明

指令扫描看不到调用，因为委托编号是 `a7` 在运行时的值。所以 SDK 中的每个 shim 都在自己的链接器段中留下一条 12 字节的声明记录，只有当该 shim 可达时，这条记录才会被保留。程序推导过程在映像中扫描这些记录，被声明的电路族加入配置，并通过映像字节受程序身份绑定。已链接但从未调用的电路族证明零个分片；如果被调用的编号所对应的电路族从未被程序声明，就没有证明。

## 六个电路

| 电路族 | 一次调用 | 构造方式 |
| --- | --- | --- |
| `KECCAK_F` | 在 51 个字的帧上执行 keccak-f[1600] 的一轮 | 字节：每轮 1,020 次 `XOR8` 查找；循环移位表示为字节与掩码副本上的线性形式 |
| `SHA256_COMP` | 四轮以及四个消息调度字 | 字节与 `XOR8`：每轮 52 项义务，每个调度字 32 项；`Ch` 和 `Maj` 表示为 XOR 的线性形式 |
| `POSEIDON2` | 一次宽度为 3 的置换 | 各轮在电路自身的层中计算，每轮三个门列表，不用查找；唯一一个在第一层之上进行计算的委托 |
| `FR_ARITH` | Montgomery 形式下的一次 `Fr` 加法、乘法或求逆 | 比特分解，以及针对 `p` 的规范性链 |
| `MOD_MUL` | 一次 256 位的 `a·b mod m`，四个以太坊模数 | 32 位 limb、一个商、进位，以及证明 `out < m` 的规范性链 |
| `EC_ADD` | secp256k1 或 BN254 G1 上一次完全点加的三分之一 | Renes–Costello–Batina 的完全加法公式，每行三次约简 |

有几种构造在它们之间反复出现。**单一编码规则**把一个指明 `k` 种情形之一的帧字解码为布尔选择子，并保证恰好只有一个被置位，因为编码会相加：没有这条规则，选择子 1 和 3 就能应答对 4 的请求。**规范性链**通过 32 位 limb 上的借位，证明一个 256 位的值小于某个模数。此外，每个被写入的字都被限定在 `2^32` 以下，使 RAM 中始终都是字，这是每个指令电路族都依赖的前提。

## 经济性

委托电路族的高度决定一个分片能容纳多少次调用，而不论占用率如何，一个分片的成本都按其高度计算：

| 电路族 | 高度 | 每分片单元数 | 分片证明 |
| --- | --- | --- | --- |
| `KECCAK_F` | `2^18` | 10,922 次置换 | 381,100 B |
| `SHA256_COMP` | `2^18` | 16,384 次压缩 | 189,988 B |
| `EC_ADD` | `2^16` | 21,845 次点加 | 434,916 B |
| `MOD_MUL` | `2^16` | 65,536 次乘法 | 135,220 B |
| `POSEIDON2` | `2^8` | 256 次置换 | 664,780 B |
| `FR_ARITH` | `2^8` | 256 次运算 | 266,292 B |

对于调用很多的电路族，更大的分片反而更便宜：`KECCAK_F` 的证明从 `2^16` 到 `2^18` 几乎没有变大。代价是内存。一个 `2^18` 的 `KECCAK_F` 分片的前向过程占 42 GiB，同时处理中的两个这样的分片决定了实测区块的内存峰值。

## 哪些被委托，哪些没有

库代码通过打过补丁的 `k256`、`ark-ff` 和 `revm-precompile` 副本用上委托：secp256k1 签名恢复变成建立在 `MOD_MUL` 和 `EC_ADD` 之上的 `k256` 代码，BN254 配对变成建立在 `MOD_MUL` 之上的 `ark-bn254` 代码。EVM 中任意模数的 `MULMOD`、`MODEXP`、BLS12-381，以及所有作为整体的签名方案，都以普通指令运行。面向客户程序的专用签名支持，是 [v2.0.0 演进方向](https://apogee.gweb3networks.com/docs/quantum-leap/signatures)的一部分。

规范见[委托 ABI](https://apogee.gweb3networks.com/docs/auditors/spec/delegation)、[委托电路](https://apogee.gweb3networks.com/docs/auditors/spec/delegation-circuits)。
