# 客户程序 SDK 参考

> guest-sdk crate 的每一个公开项，附确切的签名与行为。只有 exit 和委托 shim 会发出 ecall；其余一切都是加载和存储。

`crates/guest-sdk` 是客户程序（guest）的全部运行时：启动代码、入口宏、分配器、panic 处理程序和 ecall shim。它只为 `riscv32imac-unknown-none-elf` 编译。

## 入口

```rust
guest_sdk::entry!(main);
```

导出启动代码所调用的 `main` 符号，它是调用你的函数的包装；你的函数不带参数，返回 `()`。你的函数保留自己的名字，它本身也可以叫 `main`。从它返回即为 `exit(0)`。

## 内存区域

| 项 | 签名 | 行为 |
| --- | --- | --- |
| `public_input` | `fn public_input() -> &'static [u8]` | 公开输入的载荷，其长度字被限制在窗口范围内。不复制，不发出 ecall |
| `read_input` | `fn read_input(buf: &mut [u8]) -> usize` | 复制 `min(buf.len(), public_input().len())` 个字节并返回实际数量。返回值可能小于请求的长度 |
| `advice` | `fn advice() -> &'static [u8]` | 证明者提示（advice）的载荷，其长度被限制在区域范围内。不受任何绑定，所以由客户程序检查它。在没有提供证明者提示的运行中会导致致命的 `OutOfBounds` |
| `commit` | `fn commit(bytes: &[u8])` | 追加到公开输出（journal）并更新其长度字。宁可以 70 退出，也不会溢出 16,380 字节的窗口 |
| `journal` | `fn journal() -> &'static [u8]` | 到目前为止提交的全部内容 |
| `exit` | `fn exit(code: i32) -> !` | 结束运行，以 `code` 作为陈述的退出状态。除已提交的内容外不发布任何东西 |

## 哈希

| 项 | 签名 | 行为 |
| --- | --- | --- |
| `keccak256` | `fn keccak256(input: &[u8]) -> [u8; 32]` | 以太坊的 Keccak-256，而不是 SHA3-256。海绵结构和填充在客户程序代码中运行；每一轮 keccak-f[1600] 是一次 `KECCAK_F` 调用。如果第一次调用返回 `-ENOSYS`，则回退到软件实现 |
| `sha256` | `fn sha256(input: &[u8]) -> [u8; 32]` | FIPS 180-4 SHA-256。填充和分块循环在客户程序代码中运行；每次压缩是十六次 `SHA256_COMP` 调用。软件回退同上 |
| `poseidon2_permute` | `fn poseidon2_permute(state: &mut [u8; 96]) -> bool` | 宽度为 3 的 Poseidon2 置换，作用于三个规范的小端序 `Fr` lane，原地进行，经由 `POSEIDON2`。遇到 `-ENOSYS` 时返回 `false`，由调用方走自己的软件路径 |

## 椭圆曲线

```rust
pub type ProjectivePoint = [[u32; 8]; 3];
```

**齐次射影**坐标下的一个点，`x = X/Z`，`y = Y/Z`，每个坐标是八个小端序的 32 位 limb，数值小于曲线的域模数。它不是雅可比（Jacobian）坐标：arkworks 的 `Projective` 才是，所以从它转换的调用方，传入时映射为 `(X·Z, Y·Z², Z)`，传出时映射为 `(X·Z, Y, Z³)`。单位元是 `(0 : 1 : 0)`。

| 项 | 签名 | 行为 |
| --- | --- | --- |
| `ec_add` | `fn ec_add(codes: &[u32; 3], p: &ProjectivePoint, q: &ProjectivePoint) -> Option<ProjectivePoint>` | 用完全加法公式计算 `p + q`，按分组顺序发出三次 `EC_ADD` 调用。遇到 `-ENOSYS` 时返回 `None` |
| `ec_mul` | `fn ec_mul(codes: &[u32; 3], p: &ProjectivePoint, k: &[u32; 8]) -> Option<ProjectivePoint>` | 从最高位开始用倍加法（double-and-add）计算 `k·p`。`k` 按原样使用；把它对群阶取模是调用方的事 |
| `ec_identity` | `fn ec_identity() -> ProjectivePoint` | `(0 : 1 : 0)` |
| `recursion::SECP256K1_GROUPS`、`recursion::BN254_GROUPS` | `[u32; 3]` | 即 `codes` 参数：指定哪条曲线，形式是一次点加的三个分组选择子 |

公式证明的是算术，而不是点是否在曲线上：从证明者提示中取得的点，要自己检查。

## 原始委托 shim

`guest_sdk::recursion` 包含作用于按字对齐的帧类型的各个 shim。每个帧类型都是 `#[repr(C, align(4))]`，所以它的对齐由类型决定，而不取决于代码生成器把局部变量放在了哪里。基础格式的 shim 恰好在 `-ENOSYS` 时返回 `false`；任何其他非零应答都以 72 退出。

| 项 | 用途 |
| --- | --- |
| `mod_mul(&mut ModMulFrame) -> bool` | 一次 `a·b mod m`。用 `ModMulFrame::of(modulus, &a, &b)` 构建帧，读取 `frame.result()`。模数代码为 `SECP256K1_P`、`SECP256K1_N`、`BN254_P` 和 `BN254_R`，两个操作数都必须已经小于模数 |
| `sha256_comp(&mut Sha256Frame) -> bool` | 一次完整的压缩：按顺序发出十六次调用。先 `Sha256Frame::of(&state, &block)`，再 `frame.working()`；把结果加到链值上由调用方负责 |
| `ec_add_complete(&mut EcAddFrame, &[u32; 3]) -> bool` | 一次完全点加：按分组顺序发出三次调用。先 `EcAddFrame::of(&codes, &p, &q)`，再 `frame.result()` |
| `poseidon2(&mut Poseidon2Frame) -> bool`、`fr_arith(&mut FrArithFrame) -> bool` | 作用于字节帧的置换和一次 `Fr` 运算；`field` 和 `transcript` 会替你调用它们 |
| `sha256_rounds`、`ec_add` | 上述操作的单个步骤。顺序错误的步骤不会被拒绝，只会算出别的东西，所以优先使用完成整个操作的函数 |
| `fr_op`、`p2_field`、`field_io`、`fq_op`、`import`、`import_run`、`replay` | 递归格式的协处理器调用，供递归树自己的程序使用。它们没有软件路径 |

每个 shim 从其电路族的声明记录中读取自己的 ecall 编号；声明记录是一个 12 字节的 `static`，位于它自己的链接器段中。链接一个 shim 就声明了相应的电路族；已声明但从未调用的电路族证明零个分片。

## 运行时行为

| 组成部分 | 行为 |
| --- | --- |
| 启动 | 位于 `0x0001_0000` 的 `_start` 让 `sp` 指向 `__stack_top`（`0x8000_0000`），逐字节将 `.bss` 清零，调用 `main`，若其返回则以 0 退出 |
| 分配器 | 从 `__heap_start` 向上推进，从不释放。当某个块的末端会高于 `__stack_top − 8 MiB` 或当前的 `sp` 时，以 71 退出 |
| panic 处理程序 | 以 101 退出，不写出任何内容。发生 panic 的客户程序可以被证明，并且已经发布了它提交过的内容 |
| 退出状态 | 70 公开输出溢出，71 堆耗尽，72 委托返回了错误，101 panic |

## 透明委托

代码仓库中有两个库 crate 在客户程序目标上进行委托而无需显式指名 SDK，靠的是只在该目标上生效的对 SDK 的依赖：

- `field::Fr`：加法、Montgomery 乘法（`*`、`square`、`pow` 以及各种转换）和非零元素的 `inverse` 都调用 `FR_ARITH`。使用 `Fr` 算术的客户程序会声明该电路族。
- `transcript::poseidon2_permute` 调用 `POSEIDON2`。

vendored 的 `k256`、`ark-ff` 和 `revm-precompile` 为 secp256k1、BN254 和 EVM 预编译合约做了同样的事：[委托](https://apogee.gweb3networks.com/docs/launch/delegations#vendored)。

这一切之下的 ABI 规范见[客户程序 ABI](https://apogee.gweb3networks.com/docs/auditors/spec/ecall-abi)。
