Skip to content
This repository was archived by the owner on Oct 25, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ perftool/plots/*.svg
riscv-testdata/testdata/*.o
riscv-testdata/testdata/*.s
riscv-testdata/testdata/*.i

# remove node modules
node_modules/
6 changes: 6 additions & 0 deletions book/token-wallet-example/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[book]
authors = ["Mozak team"]
language = "en"
multilingual = false
src = "src"
title = "wallet-token-example"
95 changes: 95 additions & 0 deletions book/token-wallet-example/src/CPC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
## Cross-Program Communication with `call_send` and `call_receive`
The SDK offers two functions, `call_send` and `call_receive`, that help us to emulate communication between token and wallet program.

### Understanding `call_send`
`call_send` allows a program (the caller) to invoke a function from another program (the callee).
```rust
pub fn call_send<A, R>(
caller_prog: ProgramIdentifier,
callee_prog: ProgramIdentifier,
call_args: A,
dispatch_native: impl Fn(A) -> R,
) -> R
where
A: CallArgument,
R: CallReturn, {}
```
To the developer, it behaves in same way as the code line
```rust
let output = dispatch_native(call_args)
```
While behind the scenes, during the "dry run", that is, the native execution, `call_send` would create a `CPCMessage` structure. This structure captures information about the call, including the caller and callee program identifiers, the function arguments, and the expected return value.

```rust
pub struct CPCMessage {
pub caller_prog: ProgramIdentifier,
pub callee_prog: ProgramIdentifier,
pub call_args: RawMessage,
// return value of `dispatch_native(call_args)`
pub ret: RawMessage,
}
```
This structure would be appended to `CallTape`, essentially recording the dialogue between the programs as defined by the script.

During the zkvm execution, `call_send` essentially reads off from the correct `CPCMessage` from the `CallTape` and extracts the `ret` value. This corresponds to initiating the dialogue, and acknowledging that it has received the response.

### Usage in requesting wallet for approval

Consider a scenario where a `usdc_token` program needs permission from a `alice_wallet` program to transfer USDC tokens to bob_wallet. Here's how `call_send` comes into play:

```rust

// Arguments for `approve_transfer` function in `alice_wallet`
let approve_transfer_args = wallet::MethodArgs(token_object, remitter_wallet, remittee_wallet);

// Function to call in `alice_wallet`
let approve_function = wallet::approve_transfer;

// `usdc_token` calls `alice_wallet` to request approval
let approval = call_send(
usdc_token,
alice_wallet,
approve_transfer_args,
approve_function
);
```
This example demonstrates how `usdc_token` uses `call_send` to invoke the `approve_transfer` function within `alice_wallet`, passing the necessary arguments and receiving the approval status.

### Understanding `call_receive`

The `call_receive` function complements `call_send` by enabling the callee program to receive and process the call initiated by the caller.

```rust
pub fn call_receive() -> Option<(CPCMessage, usize)>
```

During native execution, `call_receive` acts as a placeholder, returning `None` since the corresponding `call_send` is assumed to have already recorded the dialougue.

During ZKVM execution, the program provably reads the relevant `CPCMessage` from the `CallTape`, extracting the call details and arguments. Based on the extracted information, the program executes the designated function with the provided arguments. That is, provably showing that it is following the instructions mentioned in the dialouge.

### Usage in receiving the request to approve

Continuing from the token transfer scenario, here's how `alice_wallet` uses `call_receive` to handle the request from `usdc_token`:


```rust
// `alice_wallet` receives request for approval
// from `usdc_token_program`
let Some(message_from_token_program) = call_receive();

// the request is decoded
let CPCMessage {
caller_program: token_program,
callee_program: wallet_program,
approve_transfer_args,
approval,
} = message_from_token_program.0;

// the arguments to `approve_transfer` call are
// prepared
let MethodArgs::ApproveTransfer(token_object, alice_wallet, bob_wallet) = approve_call_args;

// the wallet calls its internal function
// to approve the transfer
approve_transfer(token_object, alice_wallet, bob_wallet);
```
4 changes: 4 additions & 0 deletions book/token-wallet-example/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Summary

- [High level overview](./high_level_overview.md)
- [Cross Program Calls](./CPC.md)
67 changes: 67 additions & 0 deletions book/token-wallet-example/src/high_level_overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## High-Level Overview of a Transaction


**Token Wallet Example:**

Imagine Alice owns USDC tokens in her wallet. She wants to transfer them to Bob's wallet. This transfer is done by the USDC token program, which can only modify USDC tokens after receiving approval from the owner's wallet program (Alice's in this case).

**Program-Level Breakdown:**

A USDC token is represented by a `StateObject` with a unique owner (`USDC token program`) and details like Alice's ownership and wallet program. The USDC program requires approval from Alice's wallet program to transfer ownership to Bob and his wallet program.

```rust
let alice_wallet: ProgramIdentifier;
let bob_wallet: ProgramIdentifier;

let usdc_token_data = USDCTokenData {
usdc_token_owner: ALICE_PUBLIC_KEY,
wallet_program: alice_wallet,
amount: 100,
};

let usdc_token_program: ProgramIdentifier;

let usdc_token_object = StateObject {
address: address_of_object_in_state_tree,
constraint_owner: usdc_token_program,
data: usdc_token_data.to_bytes(),
};
```

At a high level, the program performs the following:

1. `usdc_token_program` verifies Alice owns `usdc_token_object`.
2. It requests `alice_wallet` to "call" a function approving the transfer of `usdc_token_object` to `bob_wallet`.
3. `alice_wallet` "receives the request" and calls its approval function.
4. The approval function proves Alice possesses the pre-image hash of the public key (essentially, her private key) for `usdc_token_object`. This proof is provided as private input to the wallet program.
5. `usdc_token_program` receives the approval.
6. It then "proposes a change" to update `usdc_token_object`'s owner and wallet to Bob's.

Each program generates a zero-knowledge proof of its execution within zkVM. However, this isn't enough! The proof needs to capture the communication and interaction with the global state between programs.

**Cross-Program Communication (CPC):**

Programs don't actually "call" or "receive responses" in the traditional sense. Instead, they demonstrate following a predetermined script defining their interactions. This script, called `CallTape`, ensures both programs adhere to the agreed-upon communication protocol. Our proof system verifies the script's execution.

For example, when the token program requests approval from Alice's wallet program, `CallTape` would record the following event:

- `usdc_token_program` called `alice_wallet` to execute `approve_transfer` with arguments `(usdc_token_object, bob_wallet)`.
- `alice_wallet` approved the transfer, returning `true`.

**Provable State Interaction:**

Similar to `CallTape`, a `EventTape` stores all reads and writes a program performs on the global state. The program's proof also verifies the events on `EventTape` that it claims to have emitted.

For instance, when the `usdc_token_program` wants to transfer ownership of `usdc_token_object` to Bob, `EventTape` would record:

- `usdc_token_program` proposed an update to `usdc_token_object`'s `owner` field to `BOB_PUBLIC_KEY` and `wallet` field to `bob_wallet`.

**Script Generation:**

The script is essentially created by performing a "dry run" of all programs, mimicking their regular execution as Rust programs. This is called **Native Execution**. During this execution, the script is generated, and all cross-program calls and global state interactions are recorded in `CallTape` and `EventTape`, respectively.

**Following the Script:**

We have another execution environment called **ZKVM Execution** for generating proofs. After preparing the tapes, programs are run individually on zkVM. When a program needs to communicate with another, it reads from `CallTape` and provably accesses the agreed-upon response. It then continues its execution based on the response.

**TODO:** Transaction Bundle