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 11 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/
1 change: 1 addition & 0 deletions book/token-wallet-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
book
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 = ["codeblooded1729"]
language = "en"
multilingual = false
src = "src"
title = "wallet-token-example"
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)
- [token program](./token.md)
58 changes: 58 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,58 @@
# High level overview

Consider the following scenario.

Alice owns a USDC token in her wallet. She has to transfer the token to Bob, who has his own wallet.

A USDC token is represented as `StateObject` with the constraint owner being the USDC token program represented through `ProgramIdentifier`. The USDC token, along with the amount, also stores the details of its owner (in this case, Alice) and her wallet program. The USDC program would require approval from Alice's wallet program in order to transfer the economic ownership to Bob and his wallet program.

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

let usdc_token_object_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_object_data.to_bytes(),
}
```

At a high level, the programs are responsible for the following:

- `usdc_token_program` :
- - request `alice_wallet` to "make a call" to function that approves the transfer of `usdc_token_object` to `bob_wallet`.
- - gets "output of call", that is, approval of the transfer
- "propose a change" of `usdc_token_object`'s owner and wallet to that of Bob
- `alice_wallet`
- - receives, from `usdc_token_program` "request to call" the function to approve transfer of `usdc_token_object `to `bob_wallet`
- the function execution shows ownership of `usdc_token_object` by proving knowledge of private key corresponding to public key of `usdc_token_object`, which is supplied by Alice to the program through private tape
- sends "output of call", that is the approval of transfer back to `usdc_token_program`

But what exactly would happen when we say "make a call", get or send "output of call" and "propose a change" occur?
In practice, these won't occur in the usual sense. "Make a call" or send "output of call" don't correspond to a program calling another program or sending a response to other program. "Propose a change" does not refer to sending it to some entity who is listening in.

What would actually happen is that these programs would demonstrate that they have actually followed a script together, complied with each other's requests, and sent and received the intended responses. Each of the programs continues the execution as if it had "made the call" with correct arguments, computed the correct "output of call", and sent the intended response and "proposed" the intended state change.
This entire script is stored in two parts. `CallTape` is the part where the "make a call" and "output of call" events are stored. `EventTape` is the part where all the proposed changes to final state are stored.

We briefly describe how these tapes are created. The idea is that the play is performed, and then the script is created. Each program has two types of execution, native and zkvm. In the native execution all the "make a call", sending and receiving of "output of call" and "proposal to state change" are emulated in the intended manner, and the `CallTape `and `EventTape `is generated. In the zkvm execution, the actual functions mentioned in the `CallTape ` are executed by corresponding program, and their output is shown to be the same as the ones mentioned in the `CallTape`.'

In this scenario, the `CallTape` would attest to following events

- `usdc_token_program` called `alice_wallet` to execute the `approve_transfer` function with arguments `(usdc_token_object, bob_wallet)`
- `alice_wallet` program approved the transfer, and returned the boolean value `true`
- `usdc_token_program` read the response `true`.

The `EventTape` would attest to the following events

- `usdc_token_program` read `usdc_token_object` from the global state (before seeking approval from wallet program)
- `usdc_token_program` proposed an update to `usdc_token_object` while updating owner's public key, and wallet to that of Bob (after getting approval from the wallet program)

A zkvm execution of both programs produces a proof of their execution. The proof, along with attestation to `CallTape`, confirms that the programs followed the script mentioned in `CallTape`, that is complied with each other's requests and computed the correct function with correct output.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions book/token-wallet-example/src/token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Token program

```rust


pub fn main() {
// signals initiation of program
// Null program in some sense, provides the arguments
// to the token program, which are supplied by Alice
if let Some(message_from_null_to_token) = call_receive() {
let CPCMessage {
caller_program: null_program,
callee_program: token_program,
args: transfer_call_args,
ret: transfer_done,
} = message_from_null_to_token.0;

if null_program != ProgramIdentifier::default() {
panic!("Caller is not the null program");
};

// arguments supplied by alice
// id: token_program
// token_object: USDC token owned by alice
// remitter: alice's wallet program
// remittee: bob's wallet program
// remittee_public_key: bob's public key
let MethodArgs::Transfer(id, token_object, alice_wallet, bob_wallet, bob_public_key) =
transfer_call_args;

// assert that token object is owned by the token program
assert_eq!(token_program, token_object.constraint_owner);

// token program calls its transfer function on args supplied by Alice
let success = transfer(id, token_object, alice_wallet, bob_wallet);

if success = transfer_done {
panic!("Transfer failed");
}
} else {
panic!("No One called the program");
}
}

pub fn transfer(
token_program: ProgramIdentifier,
token_object: StateObject,
remitter_wallet: ProgramIdentifier,
remittee_wallet: ProgramIdentifier,
remittee_public_key: PublicKey,
) -> bool {
// set up the args for approve_transfer function to be called by wallet program
let approve_transfer_args = Wallet::MethodArgs::ApproveTransfer(
token_object,
remitter_wallet,
remittee_wallet,
remittee_public_key,
);
let approve_transfer_return = wallet::MethodReturns::ApproveTransfer(true);

// the native function that is run by wallet program
// This would be executed during native execution
// and facilitate generation of calltape
let native_approve_transfer = Wallet::approve_transfer;

let wallet::MethodReturns::ApproveTransfer(success) = call_send(
token_program,
remitter_wallet,
approve_transfer_args,
native_approve_transfer,
approve_transfer_return,
);

if success == approve_transfer_return {
let new_token_object = StateObject {
data: TokenData {
owner: remittee_public_key,
wallet: remittee_wallet,
amount: token_object.data.amount,
},
};

event_emit(self_prog_id, Event::UpdatedStateObject(new_token_object));
}
}

```
36 changes: 36 additions & 0 deletions book/token-wallet-example/src/wallet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Wallet program

```rust
pub fn main() {
if let Some(message_from_token_program) = call_receive() {
let CPCMessage {
token_program,
wallet_program,
approve_call_args,
approval,
} = message_from_token_program.0;

let MethodArgs::ApproveTransfer(token_object, alice_wallet, bob_wallet) = approve_call_args;

assert_eq!(
approval,
approve_transfer(token_object, alice_wallet, bob_wallet)
)
} else {
panic!("Failed to receive message from token program")
}
}

pub fn approve_transfer(
token_object: StateObject,
remitter_wallet: ProgramIdentifier,
remittee_wallet: ProgramIdentifier,
) -> bool {
let (mut public_tape, mut private_tape) = get_tapes();
let private_key = PrivateKey::from(private_tape);
let token_object_data = token_object.data.into();
let public_key = token_object_data.public_key;
private_key == poseidon2_hash(public_key)
}

```
41 changes: 41 additions & 0 deletions examples/wallet/main_mozak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#![no_main]
#![feature(restricted_std)]

mod core_logic;

use mozak_sdk::io::{get_tapes, Extractor};

pub fn main() {

let Some(message_from_token_program) = call_receive(){
let CPCMessage{
token_program,
wallet_program,
approve_call_args,
approval,
} = message_from_token_program.0;

let MethodArgs::ApproveTransfer(token_object, remitter_wallet, remittee_wallet) = approve_call_args;
return approval == approve_transfer(token_object, remitter_wallet, remittee_wallet);
}
}

pub fn approve_transfer(
token_object: StateObject,
remitter_wallet: ProgramIdentifier,
remittee_wallet: ProgramIdentifier) -> bool {

let (mut public_tape, mut _private_tape) = get_tapes();

let private_key = PrivateKey::from(_private_tape);
let token_object_data = token_object.data.into();
let public_key = token_object_data.public_key;
assert_eq!(remitter_wallet, token_object_data.wallet);
return private_key == poseidon2_hash(public_key);
}

core_logic.approve_transfer(token_object, remitter_wallet, remittee_wallet);
}

// We define `main()` to be the program's entry point.
guest::entry!(main);