# 快速上手

> 从一个空 crate 到一份通过验证的证明。一个三行的客户程序，经过构建、运行、检查和证明，附上每一步的真实输出。

本页用一个最小的、确实做了点事情的客户程序（guest），把整个流程完整走一遍：它读取自己的公开输入，并把它作为公开输出（journal）发布。下面的每一段输出，都是在远地虚拟机 v1.0.0 上原样运行这些命令得到的。

> [!NOTE]
> **前提条件。** 一份检出到 v1.0.0 的远地虚拟机代码仓库，以及 `rustup`；其余一切都由仓库固定。除非某一步切换了目录，命令都在仓库根目录下运行。第 5 步和第 6 步还需要仪式文件 `assets/ptau/ppot_0080_24.ptau`，第 6 步还需要一台有数十 GiB 内存的机器。[环境准备](https://apogee.gweb3networks.com/docs/launch/setup)对这两项都有说明。

### 创建客户程序

客户程序是 `guests/` 工作空间中的一个 `no_std` 二进制 crate。创建 `guests/hello`：

```toml title="guests/hello/Cargo.toml"
[package]
name = "hello"
version.workspace = true
edition.workspace = true
publish.workspace = true

[dependencies]
guest-sdk.workspace = true
```

```rust title="guests/hello/src/main.rs"
#![no_std]
#![no_main]

guest_sdk::entry!(main);

fn main() {
    // The public input is memory: a slice, with no ecall and no cursor.
    guest_sdk::commit(guest_sdk::public_input());
}
```

使用 `#![no_std]`，是因为目标是裸机。使用 `#![no_main]` 加 `entry!(main)`，是因为 SDK 的启动代码会设置栈指针、把 `.bss` 清零，然后调用一个 `main` 符号，这个符号由宏包装你的函数后导出。从你的函数返回即为 `exit(0)`。

### 加入客户程序工作空间

在 `guests/Cargo.toml` 的 `members` 列表末尾加上 `"hello"`：

```toml title="guests/Cargo.toml"
members = ["fib", "echo", … , "recursion", "hello"]
```

### 构建

在客户程序自己的目录下构建，除了目标之外不加任何参数：

```sh
cd guests/hello
cargo build --release --target riscv32imac-unknown-none-elf
cd ../..
```

ELF 生成在 `guests/target/riscv32imac-unknown-none-elf/release/hello`。客户程序工作空间已经提供了链接脚本和 `--no-relax`，所以无需再传任何参数。

### 运行

周期分析器在远地虚拟机的模拟器中运行客户程序，不生成证明，并报告周期都花在了哪里：

```sh
printf 'hello, apogee' > /tmp/hello.in
cargo run --release -p profiler -- elf guests/target/riscv32imac-unknown-none-elf/release/hello --input /tmp/hello.in
```

```text
workload
  label                        hello
  guest                        hello
  guest cycles                 114
  exit status                  0
  journal bytes                13

cycles by family
  ADD_SUB_LUI_AUIPC            64
  JUMP_BRANCH_SLT              21
  MEM_WORD                     3
  MEM_SUBWORD                  26
```

一共执行了 114 条指令，每一条都将成为一行被证明的数据。公开输出的 13 个字节就是原样回显的输入。26 行 `MEM_SUBWORD` 来自 `commit` 用 `lbu` 和 `sb` 逐字节复制输入。

### 查看虚拟机将要证明的内容

```sh
cargo run --release -p artifact-dump -- tables \
    guests/target/riscv32imac-unknown-none-elf/release/hello \
    --ptau assets/ptau/ppot_0080_24.ptau
```

```text
program identity  9ead85cee880df30daa8eba657316215107a075640a64ccf2424a054b758b802

VmConfig
--------
  id  family              height     live rows  columns
   0  ADD_SUB_LUI_AUIPC     4194304         33  pc next_pc rs1 rs2 rd imm extra_mask
   1  JUMP_BRANCH_SLT       4194304         12  pc next_pc rs1 rs2 rd imm extra_mask
   4  MEM_WORD              4194304          3  pc next_pc rs1 rs2 rd imm extra_mask
   5  MEM_SUBWORD           4194304          3  pc next_pc rs1 rs2 rd imm extra_mask
   7  INIT_TEARDOWN         4194304          0  none: claims no pc
   8  ZERO_WINDOWS          4194304          0  none: claims no pc
  12  PUBLIC_INPUT             4096          0  none: claims no pc
  13  PUBLIC_OUTPUT            4096          0  none: claims no pc
  14  ADVICE_WINDOWS        4194304          0  none: claims no pc
```

这是程序在默认高度下的静态形态：其代码用到的四个指令电路族，各带一张解码表；以及每个程序都有的五个窗口电路族。**程序身份**是一个域元素，是以上全部内容的摘要。你得到的值会不同：ELF 会把绝对路径嵌入 panic 字符串，所以在另一台机器上构建得到的是另一个映像；而高度的每一次改变，都会得到另一个程序身份。

### 证明与验证

由宿主程序（host）请求证明。把它作为示例放在宿主程序 SDK 旁边：

```rust title="crates/host/examples/prove_hello.rs"
use constants::family;
use emulator::GuestIo;
use program::ProgramParams;
use srs::Srs;

fn main() {
    let elf = std::fs::read("guests/target/riscv32imac-unknown-none-elf/release/hello")
        .expect("build the guest with --release first");

    // Small heights for a small program: the seven instruction families at
    // their 2^20 floor, the three RAM-window families at 2^16. Every choice of
    // heights is its own program identity.
    let mut params = ProgramParams::defaults();
    for f in 0..7 {
        params.heights[f] = 1 << 20;
    }
    for f in [family::INIT_TEARDOWN, family::ZERO_WINDOWS, family::ADVICE_WINDOWS] {
        params.heights[f as usize] = 1 << 16;
    }

    // As many ceremony powers as the tallest family has rows: 2^20 here.
    let ptau = std::path::Path::new("assets/ptau/ppot_0080_24.ptau");
    let srs = Srs::from_ptau(ptau, 20).expect("the ceremony file reads");
    let setup = host::setup(&elf, &params, srs).expect("the program registers");

    let io = GuestIo { input: b"hello, apogee".to_vec(), advice: Vec::new() };
    let proven = host::prove(&setup, &io, 2).expect("the run proves"); // two shards in flight
    host::verify(&setup.vk, &proven.block).expect("the block verifies");

    assert_eq!(proven.exit_code, 0);
    assert_eq!(proven.journal, b"hello, apogee");
    let id: String = setup.vk.identity.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    println!("identity  {id}");
    println!("cycles    {}", proven.cycles);
    println!("shards    {}", proven.report.shards);
    println!("journal   {:?}", core::str::from_utf8(&proven.journal).unwrap());
}
```

```sh
cargo run --release -p host --example prove_hello
```

```text
identity  606d1f1d720459cc1a078787381656b29c9fce5a9e539b36f899e62b64129c14
cycles    114
shards    7
journal   "hello, apogee"
```

在一台 18 核、48 GiB 内存的笔记本电脑上，这一步耗时 52 秒，内存峰值 18 GB，几乎全部来自同时处理中的两个 `2^20` 分片。这里的程序身份与第 5 步的不同，因为高度不同：程序身份绑定每一个高度。

### 保管程序身份

验证者从不从证明、密钥或证明者那里获取程序身份。它持有自己的副本，从构建该发布版本的一方取得，并进行比对：

```rust
assert_eq!(setup.vk.identity.to_bytes(), registered); // `registered` from your own channel
```

如果对照的是证明者提供的身份，证明只能说明*某个*程序运行过。

## 刚才发生了什么

模拟器把这 114 条指令执行了两遍。第一遍承诺了每个分片的内存列，并确定了陈述；第二遍填充每个分片并证明它。一共七个分片：执行过的四个指令电路族各一个，存放程序映像的内存窗口一个，公开输入和公开输出各一个。这个客户程序从未触及自己的栈，所以没有其他窗口需要分片；典型的程序还会多出栈所在窗口的分片。每个分片都由其电路族的 GKR 电路证明，并用一个 Mercury 证明打开；验证者用一个等式核对了全部七个分片的内存读写。[系统架构概述](https://apogee.gweb3networks.com/docs/architecture)沿着同一条路径给出了详细说明。

## 下一步

- [编写客户程序](https://apogee.gweb3networks.com/docs/launch/write): crate 结构、依赖、堆，以及在宿主机上测试。

- [输入、证明者提示与公开输出](https://apogee.gweb3networks.com/docs/launch/io): 数据如何进出，以及证明绑定了什么。

- [链上结算](https://apogee.gweb3networks.com/docs/launch/on-chain): 从一份块证明，到一个返回 true 的合约。
