Skip to content

Commit 7735cfe

Browse files
nyc432claude
andcommitted
refactor: Remove legacy code and unnecessary pickle support (v0.2.0)
Major cleanup and simplification: - Remove all pickle support from PyO3 classes (unused complexity) - Delete serialization.rs (was only for pickle support) - Remove legacy compatibility methods: - journal_bytes() (use journal property instead) - image_id property (use id property instead) - ed25519_input_vecs() (use ed25519_input() instead) - Clean up serialization helpers: - Remove incompatible merkle_proof_input() for env::read() style - Keep merkle_commitment_input() for 2LA-style demo - Update demos to use serialization helpers properly Benefits: - Smaller codebase with less maintenance burden - Clearer separation between Rust and Python code - No confusing duplicate methods or unused features - Demos now properly test the serialization helpers Breaking changes from v0.1.0: - Removed pickle support (use standard serialization if needed) - Removed deprecated method aliases 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 04dfca9 commit 7735cfe

File tree

13 files changed

+209
-215
lines changed

13 files changed

+209
-215
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[package]
44
name = "pyr0"
5-
version = "0.1.0"
5+
version = "0.2.0"
66
edition = "2021"
77

88
[lib]

README.md

Lines changed: 169 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# PyR0 - Python Interface for RISC Zero zkVM
22

3-
[![Version](https://img.shields.io/badge/version-0.1.0-orange)](https://github.com/garyrob/PyR0/releases)
3+
[![Version](https://img.shields.io/badge/version-0.2.0-orange)](https://github.com/garyrob/PyR0/releases)
44
**⚠️ Experimental Alpha - Apple Silicon Only**
55

66
Python bindings for [RISC Zero](https://www.risczero.com/) zkVM, enabling zero-knowledge proof generation and verification from Python.
77

8-
> **Note**: This is an experimental alpha release (v0.1.0) currently targeting Apple Silicon (M1/M2/M3) Macs only.
8+
> **Note**: This is an experimental alpha release (v0.2.0) currently targeting Apple Silicon (M1/M2/M3) Macs only.
99
1010
## Overview
1111

@@ -15,6 +15,20 @@ PyR0 provides a Python interface to RISC Zero's zero-knowledge virtual machine,
1515
- Build Merkle trees with Poseidon hash for ZK circuits
1616
- Serialize data for guest program inputs
1717

18+
### Architecture
19+
20+
PyR0 bridges Python and RISC Zero zkVM:
21+
22+
1. **Host (Python)**: Prepares input data, loads guest programs, generates proofs
23+
2. **Guest (Rust)**: Executes in zkVM, reads input, writes output to journal
24+
3. **Proof**: Cryptographic evidence that the guest executed correctly
25+
26+
The typical workflow:
27+
1. Write a Rust guest program that performs your computation
28+
2. Compile it to RISC-V ELF binary
29+
3. Use PyR0 to load the ELF, provide input, and generate a proof
30+
4. Share the proof and journal (public outputs) for verification
31+
1832
## Installation
1933

2034
### System Requirements
@@ -94,23 +108,100 @@ hash_result = merkle_py.poseidon_hash([input1, input2])
94108

95109
### Data Serialization
96110

97-
Prepare data for RISC Zero guest programs:
111+
PyR0 provides serialization helpers for sending data to RISC Zero guest programs. The choice of serialization method depends on how your guest reads the data:
112+
113+
#### For Guests Using `env::read()`
114+
115+
These helpers add length prefixes and are designed for serde deserialization:
98116

99117
```python
100118
from pyr0 import serialization
101119

102-
# Optional serialization helpers for guest programs
120+
# These work with env::read() in the guest
103121
data = serialization.to_vec_u8(bytes_data) # Vec<u8> with length prefix
104-
data = serialization.to_u32(value) # 32-bit unsigned integer
105-
data = serialization.to_u64(value) # 64-bit unsigned integer
106-
data = serialization.to_string(text) # String with length prefix
107-
data = serialization.to_bool(value) # Boolean value
108-
data = serialization.to_bytes32(data) # Fixed [u8; 32] array
109-
data = serialization.to_bytes64(data) # Fixed [u8; 64] array
110-
111-
# Convenience functions for common patterns
122+
data = serialization.to_u32(value) # u32 (4 bytes, little-endian)
123+
data = serialization.to_u64(value) # u64 (8 bytes, little-endian)
124+
data = serialization.to_string(text) # String with 8-byte length prefix
125+
data = serialization.to_bool(value) # bool (single byte: 0 or 1)
126+
127+
# Guest code would use:
128+
# let data: Vec<u8> = env::read();
129+
# let value: u32 = env::read();
130+
# let text: String = env::read();
131+
```
132+
133+
#### For Guests Using `env::read_slice()`
134+
135+
These helpers provide raw bytes without length prefixes:
136+
137+
```python
138+
# Fixed-size arrays (no length prefix)
139+
data = serialization.to_bytes32(data) # [u8; 32] - exactly 32 bytes
140+
data = serialization.to_bytes64(data) # [u8; 64] - exactly 64 bytes
141+
data = serialization.raw_bytes(data) # Raw bytes, no transformation
142+
143+
# Guest code would use:
144+
# let mut buffer = [0u8; 32];
145+
# env::read_slice(&mut buffer);
146+
```
147+
148+
#### Convenience Functions
149+
150+
Pre-built serializers for common cryptographic operations:
151+
152+
```python
153+
# For Ed25519 signature verification (uses env::read() format)
112154
input_data = serialization.ed25519_input(public_key, signature, message)
113-
input_data = serialization.merkle_proof_input(leaf, siblings, indices)
155+
156+
# For Merkle proofs with 2LA-style commitments (uses env::read_slice() format)
157+
# Note: Expects exactly 16 siblings for a 16-level tree
158+
input_data = serialization.merkle_commitment_input(k_pub, r, e, siblings, indices)
159+
```
160+
161+
#### Choosing Between `env::read()` and `env::read_slice()`
162+
163+
**Use `env::read()` when:**
164+
- Data sizes are variable
165+
- You want automatic deserialization
166+
- You're using standard Rust types (Vec, String, etc.)
167+
- You don't know the exact size in advance
168+
169+
**Use `env::read_slice()` when:**
170+
- Data size is fixed and known
171+
- You want maximum efficiency (no overhead)
172+
- You're working with raw bytes
173+
- You want to avoid serde overhead
174+
175+
**Example: Sending mixed data types**
176+
177+
```python
178+
# Python host code
179+
from pyr0 import serialization
180+
181+
# Combine different serialization methods
182+
input_data = b""
183+
input_data += serialization.to_u32(42) # 4 bytes (for env::read)
184+
input_data += serialization.to_bytes32(key) # 32 bytes (for env::read_slice)
185+
input_data += serialization.to_string("hello") # Variable (for env::read)
186+
187+
receipt = pyr0.prove(image, input_data)
188+
```
189+
190+
```rust
191+
// Rust guest code
192+
use risc0_zkvm::guest::env;
193+
194+
fn main() {
195+
// Read the u32 with serde
196+
let number: u32 = env::read();
197+
198+
// Read the fixed array with read_slice
199+
let mut key = [0u8; 32];
200+
env::read_slice(&mut key);
201+
202+
// Read the string with serde
203+
let text: String = env::read();
204+
}
114205
```
115206

116207
### Journal Deserialization
@@ -166,6 +257,60 @@ PyR0/
166257
└── CLAUDE.md # Development notes
167258
```
168259

260+
## Common Pitfalls
261+
262+
### Serialization Mismatches
263+
264+
The most common error is mismatched serialization between host and guest:
265+
266+
```python
267+
# WRONG: Host uses to_vec_u8 (adds length prefix)
268+
input_data = serialization.to_vec_u8(my_bytes)
269+
```
270+
271+
```rust
272+
// WRONG: Guest uses read_slice (expects raw bytes)
273+
let mut buffer = [0u8; 32];
274+
env::read_slice(&mut buffer); // Will read length prefix as data!
275+
```
276+
277+
**Solution**: Match serialization methods:
278+
- `to_vec_u8()``env::read::<Vec<u8>>()`
279+
- `to_bytes32()` or `raw_bytes()``env::read_slice()`
280+
281+
### Size Mismatches
282+
283+
When using `env::read_slice()`, the buffer size must match exactly:
284+
285+
```rust
286+
// Guest expects exactly 100 bytes
287+
let mut buffer = [0u8; 100];
288+
env::read_slice(&mut buffer);
289+
```
290+
291+
```python
292+
# Host must send exactly 100 bytes
293+
input_data = serialization.raw_bytes(b"x" * 100) # Correct
294+
input_data = serialization.raw_bytes(b"x" * 99) # Wrong - too short!
295+
```
296+
297+
### Journal Format
298+
299+
The journal (public output) should use a consistent serialization format. We recommend Borsh for cross-language compatibility:
300+
301+
```rust
302+
// Guest: Write with Borsh
303+
use borsh::BorshSerialize;
304+
let output = MyOutput { ... };
305+
env::commit_slice(&borsh::to_vec(&output)?);
306+
```
307+
308+
```python
309+
# Host: Read with Borsh
310+
from borsh_construct import CStruct
311+
output = OutputSchema.parse(receipt.journal)
312+
```
313+
169314
## Development
170315

171316
This project uses [maturin](https://www.maturin.rs/) for building Python extensions from Rust. Key commands:
@@ -174,12 +319,23 @@ This project uses [maturin](https://www.maturin.rs/) for building Python extensi
174319
# Build release wheel
175320
uv tool run maturin build --release
176321

322+
# Install the built wheel (force reinstall to avoid cache issues)
323+
uv pip install --force-reinstall target/wheels/PyR0-0.2.0-*.whl
324+
177325
# Run tests/demos
178326
uv run demo/ed25519_demo.py
179327
uv run demo/merkle_zkp_demo.py
180328
uv run test/test_merkle_zkp.py
181329
```
182330

331+
### Important Build Notes
332+
333+
After making changes to ANY file (Rust or Python), you must rebuild and reinstall:
334+
1. Build: `uv tool run maturin build --release`
335+
2. Install: `uv pip install --force-reinstall target/wheels/PyR0-*.whl`
336+
337+
The `--force-reinstall` flag is crucial to ensure you're using the latest version.
338+
183339
## Acknowledgments
184340

185341
This project is based on the original [PyR0](https://github.com/l2iterative/pyr0prover-python) by L2 Iterative, which focused on distributed proof generation using Dask/Ray. The current fork extends PyR0 with additional features for zero-knowledge proof development, including Merkle tree support and improved guest program interfaces.

demo/merkle_zkp_demo.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,22 +144,18 @@ def run_zkp_proof(selected_user, merkle_siblings, merkle_bits, tree_root):
144144
print(f" e: 0x{selected_user['e'].hex()[:16]}... (secret!)")
145145
print(f" Merkle path: {len(merkle_siblings)} siblings (secret!)")
146146

147-
# Prepare input for the guest program - now using raw bytes
148-
# Guest uses env::read_slice() for efficient reading
149-
input_data = b""
147+
# Use the serialization helper to prepare input
148+
# This creates the exact format expected by the guest program
149+
from pyr0 import serialization
150150

151-
# k_pub, r, e - raw 32 bytes each
152-
input_data += selected_user['k_pub']
153-
input_data += selected_user['r']
154-
input_data += selected_user['e']
155-
156-
# Path siblings - 16 * 32 bytes
157-
for sibling in merkle_siblings:
158-
input_data += sibling
159-
160-
# Indices - 16 bytes (one byte per bit)
161-
for bit in merkle_bits:
162-
input_data += bytes([1 if bit else 0])
151+
# Use the merkle commitment helper for guests using env::read_slice()
152+
input_data = serialization.merkle_commitment_input(
153+
selected_user['k_pub'],
154+
selected_user['r'],
155+
selected_user['e'],
156+
merkle_siblings,
157+
merkle_bits
158+
)
163159

164160
# Debug: Check the data
165161
print(f"\nDebug - Input data size: {len(input_data)} bytes")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "PyR0"
7-
version = "0.1.0"
7+
version = "0.2.0"
88
authors = [
99
{ name = "Weikeng Chen", email = "weikeng.chen@l2iterative.com" }
1010
]

src/image.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::serialization::Pickleable;
21
use anyhow::Result;
32
use pyo3::prelude::*;
43
use risc0_binfmt::{MemoryImage, Program};
@@ -29,7 +28,6 @@ impl Image {
2928
}
3029
}
3130

32-
impl Pickleable for Image {}
3331

3432
#[pymethods]
3533
impl Image {
@@ -51,19 +49,5 @@ impl Image {
5149
))
5250
}
5351
}
54-
55-
/// Legacy alias for compatibility
56-
#[getter]
57-
fn image_id(&self) -> PyResult<Vec<u8>> {
58-
self.id()
59-
}
6052

61-
fn __getstate__(&self, py: Python<'_>) -> PyResult<PyObject> {
62-
self.to_bytes(py)
63-
}
64-
65-
fn __setstate__(&mut self, py: Python<'_>, state: PyObject) -> PyResult<()> {
66-
*self = Self::from_bytes(state, py)?;
67-
Ok(())
68-
}
6953
}

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
mod image;
22
mod receipt;
33
mod segment;
4-
mod serialization;
54
mod session;
65
mod succinct;
76

0 commit comments

Comments
 (0)