l2iterative / bonsai-sdk-php

Bonsai SDK for PHP

1.2.0 2024-01-07 10:47 UTC

This package is not auto-updated.

Last update: 2024-09-29 20:40:37 UTC


README

a guy sitting on the subway station waiting for the train to arrive

当前的 Bonsai API 实现是用 Rust 编写的。由于 Bonsai 需要API密钥并且会消耗配额来生成零知识证明,因此使用 Bonsai 构建的web3应用程序需要后端。

与大多数web3应用程序一样,后端可以是极简的,因为在前端,大部分链上交互可以通过例如 Metamask的以太坊提供者API 完成。

后端将是什么样子?

出于上述原因,后端通常分为以下六类

    • 纯 Typescript 实现,可能使用 WebAssembly,通常通过 Rust 实现(见 wasm-bindgen
    • 纯 PHP 实现
    • 纯 Python 实现,如 DjangoFlask
  • 使用 FFI(外部函数接口)
    • 使用 FFI 的 TypeScript 实现,FFI 用于可能用 Rust 编写的高性能代码,通过 ffi-rskoffi
    • 使用 FFI 的 PHP 实现,可能通过 FFI 扩展
    • 使用 FFI 的 Python 实现,可能通过 CFFI

许多因素会影响选择哪一种

  • 网络托管提供商的可用性。
    • 有许多价格合理的 TypeScript 和 PHP 网络托管平台。
    • 运行 Python,传统上使用完整的云虚拟机(如 AWS EC2 实例)。出于成本和自动扩展能力的原因,倾向于避免使用完整的虚拟机。
  • 性能。
    • 每个人都同意 Python 很慢。
    • PHP 可以通过使用 OPcache 来预编译脚本进行优化,但这不是机器代码。Typescript 可以通过 v8 编译(与 WebAssembly 一起),我们期望它将在“纯”组中拥有最佳性能。
    • 然而,当我们可以使用 FFI 时,这变得不那么相关了,因为人们也可以运行机器代码,而且与即时编译(JIT)生成的代码不同,这种代码是 提前编译(AOT),并且预期会有更好的性能,并且在可用时应该是首选。
  • 开发友好性。
    • 可能最重要的是:理想情况下,学习周期少,不需要进行任何修改。
    • Rust 在这里很有用,因为 RISC Zero 程序现在是用 Rust 编写的,并且 Rust 中的 WebAssembly 已被用于 web 开发,尤其是密码学。
    • 另一种编程语言与 Rust 更相似。在这三者中,PHP 与 Rust 最相似。PHP 以非常友好地对初学者为特点。
    • 所有这些都提供了 Web3 SDK 实现,但 TypeScript 现在的支持最好。

了解这一点很重要,即这个后端将采用极简主义风格。我们之所以有后端,是因为API密钥不能暴露给客户端。后端可能仅包括

  • 对数据库的访问,例如 MySQL,用于链下数据存储
  • 与垃圾邮件防止系统的集成,例如 Cloudflare TurnstileGoogle reCAPTCHA
  • 连接到RISC Zero Bonsai API,这也是这个仓库的目的

如何使用

文档可以在 docs 文件夹中找到。它设计得与这里的Bonsai Rust API相似

https://github.com/risc0/risc0/blob/main/bonsai/sdk/src/alpha.rs

集成测试可以作为教程。它可以像以下这样简单

<?php

use L2Iterative\BonsaiSDK\Client;
use L2Iterative\BonsaiSDK\SessionId;
use L2Iterative\BonsaiSDK\SnarkId;

$client = new Client("https://api.bonsai.xyz", $test_key, $test_version);

$input_id = $client->upload_input($input_data);
$session_id = new SessionId($client->create_session($test_img_id, $input_id, []));

$status = NULL;
while(true) {
    usleep(300);
    $status = $session_id->status($client);
    
    if($status->status != 'RUNNING') {
        break;
    }
}

// error handling if the status is not successful

$snark_id = new SnarkId($client->create_snark($session_id->uuid));

$status = NULL;
while(true) {
    usleep(300);
    $status = $snark_id->status($client);
    
    if($status->status != 'RUNNING') {
        break;
    }
}

// error handling if the status is not successful

// the proof can be forwarded to the frontend to make RPC calls

序列化器

使用不同编程语言与RISC Zero交互时遇到的另一个挑战是关于类型。这个问题对PHP尤其重要,因为PHP没有为不同的字长提供单独的类型。

例如,在PHP中,我们只有 int 表示整数,没有其他。但这可以对应于Rust中的i8、i16、i32、i64、u8、u16、u32、u64。还有PHP难以表示的数字,如i128和u128。

由于Rust类型是这里缺失的信息,开发者别无选择,只能显式提供Rust类型。为此,开发者需要包裹所有元素。

以下是一个非常全面的示例,可以作为教程。

public function test_serialization() {
    $example = new Struct([
        'u8v' => new SameTypeArray([
            new U8(1), new U8(231), new U8(123)
        ]),
        'u16v' => new SameTypeArray([
            new U16(124), new U16(41374)
        ]),
        'u32v' => new SameTypeArray([
            new U32(14710471), new U32(3590275702), new U32(1), new U32(2)
        ]),
        'u64v' => new SameTypeArray([
            new U64(352905235952532), new U64(2147102974910410)
        ]),
        'i8v' => new SameTypeArray([
            new I8(-1), new I8(120), new I8(-22)
        ]),
        'i16v' => new SameTypeArray([
            new I16(-7932)
        ]),
        'i32v' => new SameTypeArray([
            new I32(-4327), new I32(35207277)
        ]),
        'i64v' => new SameTypeArray([
            new I64(-1), new I64(1)
        ]),
        'u8s' => new U8(3),
        'bs' => new Boolean(true),
        'some_s' => new Some(new U16(5)),
        'none_s' => new None(),
        'strings' => new BinaryString("Here is a string."),
        'stringv' => new SameTypeArray([
            new BinaryString("string a"),
            new BinaryString("34720471290497230")
        ])
    ]);

    $serializer = new Serializer();
    $serializer->serialize($example);

    $php_output = $serializer->output();
    $rust_output = [3, 1, 231, 123, 2, 124, 41374, 4, 14710471, 3590275702, 1, 2, 2, 658142100, 82167, 1578999754, 499911, 3, 4294967295, 120, 4294967274, 1, 4294959364, 2, 4294962969, 35207277, 2, 4294967295, 4294967295, 1, 0, 3, 1, 1, 5, 0, 17, 1701995848, 544434464, 1953701985, 1735289202, 46, 2, 8, 1769108595, 1629513582, 17, 842478643, 825701424, 875575602, 858928953, 48];

    $this->assertEquals($php_output, $rust_output);
}

相应的Rust结构定义如下。

#[derive(Default, Debug, Serialize)]
pub struct SA {
    pub u8v: Vec<u8>,
    pub u16v: Vec<u16>,
    pub u32v: Vec<u32>,
    pub u64v: Vec<u64>,
    pub i8v: Vec<i8>,
    pub i16v: Vec<i16>,
    pub i32v: Vec<i32>,
    pub i64v: Vec<i64>,
    pub u8s: u8,
    pub bs: bool,
    pub some_s: Option<u16>,
    pub none_s: Option<u32>,
    pub strings: String,
    pub stringv: Vec<String>
}

请注意,我们在Rust中关注的是String而不是二进制字符串,因为当前的序列化器在处理 Vec<u8> 时会有不同的麻烦。如果某个Rust代码需要使用 Vec<u8> 并且不能忍受包装器,那么迄今为止最好的解决方案是让PHP通过 str_split($str) 将字符串分解成一个字节数组。