Skip to content
Draft
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
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@

Applications built on public blockchains face a fundamental limitation: all computation is transparent. These examples demonstrate how to build applications that can compute on encrypted data while preserving privacy.

All examples use Arcium's MPC protocol, which preserves privacy even with a dishonest majority -- data remains confidential as long as one node is honest.

## Getting Started

For installation instructions and setup, see the [Installation Guide](https://docs.arcium.com/developers/installation).

## Examples
Each example follows the same build/test flow:

```bash
yarn install
arcium build
arcium test
```

New to Arcium? Start with Coinflip and progress through the tiers in order. For conceptual background, see [Mental Model](https://docs.arcium.com/developers/arcis/mental-model).
New to Arcium? Start with Coinflip and progress through the tiers. For conceptual background, see [Mental Model](https://docs.arcium.com/developers/arcis/mental-model).

## Examples

### Getting Started

**[Coinflip](./coinflip/)** - Generate trustless randomness using `ArcisRNG`. Stateless design, simplest example.
**[Coinflip](./coinflip/)** - Trustless randomness using `ArcisRNG`. Stateless design, simplest example.

**[Rock Paper Scissors](./rock_paper_scissors/)** - Encrypted asynchronous gameplay with hidden moves.
- [Player vs Player](./rock_paper_scissors/against-player/) - Two encrypted submissions
**[Rock Paper Scissors](./rock_paper_scissors/)** - Encrypted gameplay with hidden moves.
- [Player vs Player](./rock_paper_scissors/against-player/) - Two encrypted submissions, async commitment
- [Player vs House](./rock_paper_scissors/against-house/) - Provably fair randomized opponent

### Intermediate
Expand Down
133 changes: 31 additions & 102 deletions blackjack/README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,24 @@
# Blackjack - Hidden Game State

Physical blackjack naturally hides the dealer's hole card. Digital blackjack has a different problem: the card's value must be stored somewhere - whether that's on a game server, in a database, or in code. Trusting a server to "hide" it just means betting they won't peek.
Physical blackjack naturally hides the dealer's hole card. Digital blackjack has a different problem: the card's value must be stored somewhere, and whoever stores it can peek. This example keeps cards encrypted until game rules require them to be revealed.

This example shows how to implement blackjack where cards remain encrypted until they need to be revealed according to game rules.
## How It Works

## Why is hidden information hard in digital card games?
1. A 52-card deck is shuffled using Arcium's cryptographic randomness
2. The entire deck, player hand, and dealer hand remain encrypted throughout gameplay
3. Players view their own cards; the dealer's hole card stays hidden
4. Game actions (hit, stand, double down) are processed against encrypted state
5. At resolution, hands are compared inside MPC and only the winner is revealed

Physical blackjack maintains three types of hidden information: the dealer's hole card, undealt cards in the deck, and random shuffle order. In digital implementations, this information must be stored as data - whether on a server or on a public blockchain - where it becomes vulnerable to inspection or manipulation.
## Implementation

Blockchain implementations face an additional challenge: transparent state. If card data is stored on-chain unencrypted, all participants can view the dealer's hidden cards and remaining deck order, completely breaking the game.
### The Size Problem and `Pack<T>`

## How Hidden Game State Works
Encrypting 52 cards individually produces 52 x 32 = 1,664 bytes -- exceeds Solana's 1,232-byte transaction limit. Arcis `Pack<T>` compresses byte arrays into fewer field elements:

At game initialization, a 52-card deck is shuffled using Arcium's cryptographic randomness. The entire deck, including player cards and the dealer's hole card, remains encrypted throughout gameplay.

Information disclosure follows game rules: players view their own cards and the dealer's face-up card. Game actions (hit, stand, double down) are processed against encrypted hand values. The dealer's hole card and undealt cards remain encrypted until game resolution.

## Running the Example

```bash
# Install dependencies
yarn install # or npm install or pnpm install

# Build the program
arcium build

# Run tests
arcium test
```

The test suite demonstrates a complete game flow: deck shuffling with secure randomness, dealing encrypted cards, processing game actions against hidden state, and verifying final game result.

## Technical Implementation

The deck is stored as encrypted values, with multiple cards packed together for efficiency. Game logic processes encrypted hand values without ever decrypting them, using Arcium's confidential instructions.

The system works through three key mechanisms: network-generated randomness creates unpredictable deck order, selective disclosure reveals only authorized information per game rules, and the MPC protocol ensures no party can manipulate game state or outcomes even with a dishonest majority—game integrity is preserved as long as one node is honest.

## Implementation Details

### The Encryption Size Problem

**Requirement**: Encrypt a deck of 52 playing cards for an on-chain Blackjack game.

**Naive Approach**: Store each card as a separate encrypted value.

- Each card can be represented as `u8` (values 0-51)
- 52 cards = 52 `u8` values
- After encryption: each `u8` becomes a 32-byte ciphertext
- **Total size**: 52 x 32 bytes = **1,664 bytes**

**The Problem**: Solana's transaction size limit is **1,232 bytes**, but our encrypted deck is 1,664 bytes. Whether returning the generated deck from initialization or passing it in transactions to deal more cards, the deck won't fit in a single transaction.

### The Solution: `Pack<T>`

Arcis provides `Pack<T>`, a built-in type that compresses byte arrays into fewer field elements for encryption. Instead of encrypting each card individually, `Pack` packs multiple bytes into a single field element.

**How it works**:

- Each field element holds up to 26 bytes (at 8 bits per byte)
- `Pack<[u8; 52]>` = 52 bytes / 26 bytes per element = **2 field elements**
- `Pack<[u8; 11]>` = 11 bytes / 26 bytes per element = **1 field element**

**Usage in the circuit**:
- Each field element holds up to 26 bytes
- `Pack<[u8; 52]>` = 2 field elements = **64 bytes** (96% reduction)
- `Pack<[u8; 11]>` = 1 field element = **32 bytes** per hand

```rust
type Deck = Pack<[u8; 52]>;
Expand All @@ -72,67 +28,40 @@ let deck_packed: Deck = Pack::new(initial_deck);
let deck = Mxe::get().from_arcis(deck_packed);
```

**After encryption**:

- 2 field elements -> 2 x 32 bytes = **64 bytes**
- **Savings**: 1,664 bytes -> 64 bytes (96% reduction)

Accessing individual cards requires unpacking first:
Accessing individual cards requires unpacking:

```rust
let deck_array = deck_ctxt.to_arcis().unpack();
let card = deck_array[index];
```

### Account Storage Structure
> [Best Practices](https://docs.arcium.com/developers/arcis/best-practices)

### Account Storage

On-chain storage uses serialized byte arrays. Encrypted `Pack` values are stored as `[u8; 32]` ciphertexts:
Encrypted `Pack` values are stored as `[u8; 32]` ciphertexts:

```rust
pub struct BlackjackGame {
pub deck: [[u8; 32]; 2], // Pack<[u8; 52]> = 2 field elements
pub player_hand: [u8; 32], // Pack<[u8; 11]> = 1 field element
pub dealer_hand: [u8; 32], // Pack<[u8; 11]> = 1 field element
pub deck_nonce: u128, // Nonce for deck encryption
pub client_nonce: u128, // Nonce for player hand
pub dealer_nonce: u128, // Nonce for dealer hand
pub player_hand_size: u8, // How many cards actually in player_hand
pub dealer_hand_size: u8, // How many cards actually in dealer_hand
pub deck_nonce: u128,
pub client_nonce: u128,
pub dealer_nonce: u128,
pub player_hand_size: u8,
pub dealer_hand_size: u8,
// ... other game state
}
```

**Why separate nonces?** Each encrypted value needs its own nonce for security.

**Why track hand sizes?** The packed hand can hold up to 11 cards, but we need to know how many are actually present (could be 2, 3, 10, etc.).

### The Result

**Without packing**:

- Deck: 52 encrypted values (1,664 bytes)
- Player hand: 11 encrypted values (352 bytes)
- Dealer hand: 11 encrypted values (352 bytes)
- **Total**: 2,368 bytes (won't fit in Solana transaction or account efficiently)

**With `Pack<T>`**:

- Deck: 2 encrypted values (64 bytes)
- Player hand: 1 encrypted value (32 bytes)
- Dealer hand: 1 encrypted value (32 bytes)
- **Total**: 128 bytes

**Additional benefits**:

- Fewer MPC operations (4 encryptions vs 74)
- Faster computation (less encrypted data to process)
- Lower costs (fewer computation units)
- No manual encoding/decoding logic needed

> For more optimization techniques, see [Best Practices](https://docs.arcium.com/developers/arcis/best-practices).

### When to Use This Pattern
Each encrypted value needs its own nonce. Hand sizes are tracked separately because the packed array always holds 11 slots but only some contain real cards.

Use `Pack<T>` when you have arrays of small values that are processed together. Arcis handles the packing and unpacking automatically — define a type alias, and the framework manages compression.
### Size Comparison

Examples: game pieces, map tiles, bit flags, small integers, inventory items.
| | Without Pack | With Pack |
|---|---|---|
| Deck | 1,664 bytes (52 x 32) | 64 bytes (2 x 32) |
| Player hand | 352 bytes (11 x 32) | 32 bytes (1 x 32) |
| Dealer hand | 352 bytes (11 x 32) | 32 bytes (1 x 32) |
| **Total** | **2,368 bytes** | **128 bytes** |
4 changes: 2 additions & 2 deletions blackjack/encrypted-ixs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ mod circuits {
)
}

// Returns true if the player has busted
// Checks the player's current hand value; returns true if already bust (>21)
#[instruction]
pub fn player_stand(player_hand_ctxt: Enc<Shared, Hand>, player_hand_size: u8) -> bool {
let player_hand = player_hand_ctxt.to_arcis().unpack();
let value = calculate_hand_value(&player_hand, player_hand_size);
(value > 21).reveal()
}

// Returns true if the player has busted, if not, returns the new card
// Deals one card and forces stand; returns (updated hand, is_bust)
#[instruction]
pub fn player_double_down(
deck_ctxt: Enc<Mxe, Deck>,
Expand Down
82 changes: 10 additions & 72 deletions coinflip/README.md
Original file line number Diff line number Diff line change
@@ -1,91 +1,29 @@
# Coinflip - Trustless Randomness

Flip a coin online and you have to trust someone. Trust the server not to rig the flip, or trust yourself not to inspect the code and game the system. There's no way to prove it's actually random.

This example shows how to generate verifiably random outcomes using distributed entropy generation.

## Why is randomness hard to trust online?

Traditional random number generation has a fundamental trust problem: whoever generates the random number can potentially influence or predict it. Server-side RNG can be biased by operators, client-side generation can be manipulated, and pseudorandom algorithms may have predictable patterns. The requirement is generating randomness that remains unpredictable and unbiased even when participants don't trust each other.
Flip a coin online and you have to trust someone. Trust the server not to rig the flip, or trust yourself not to inspect the code and game the system. This example generates verifiably random outcomes where no single party can predict or bias the result.

## How It Works

The coinflip follows this flow:

1. **Player commitment**: The player's choice (heads/tails) is encrypted and submitted to the network
2. **Random generation**: Arcium nodes work together to generate a random boolean outcome
3. **Encrypted comparison**: The system compares the encrypted choice against the encrypted random result
4. **Result disclosure**: Only the win/loss outcome is revealed

The comparison occurs on encrypted values throughout the computation.

## Running the Example

```bash
# Install dependencies
yarn install # or npm install or pnpm install

# Build the program
arcium build

# Run tests
arcium test
```

The test suite demonstrates the complete flow: player choice submission, secure random generation, encrypted comparison, and result verification.

## Technical Implementation

The player's choice is encrypted as a boolean, allowing result verification without exposing the choice prematurely. Random generation uses Arcium's cryptographic primitives, where Arcium nodes contribute entropy that no single node can predict or control.
1. Player's choice (heads/tails) is encrypted and submitted
2. Inside one MPC computation, Arcium nodes generate a random boolean, compare it against the encrypted choice, and produce the result
3. Only the win/loss outcome is revealed

## Implementation Details
## Implementation

### The Trustless Randomness Problem

**Conceptual Challenge**: In traditional online systems, randomness comes from somewhere - a server, a third-party service, your browser's `Math.random()`. Each source requires trusting that entity:

- **Server-generated**: Trust the operator doesn't rig outcomes
- **Third-party service**: Trust the service provider is honest
- **Client-side**: Trust the player doesn't inspect and manipulate

**The Question**: Can we generate randomness where NO single party can predict or bias the outcome?

### The MPC Randomness Solution

Arcium's `ArcisRNG` generates randomness through multi-party computation:
### MPC Randomness

```rust
pub fn flip(input_ctxt: Enc<Shared, UserChoice>) -> bool {
let input = input_ctxt.to_arcis();
let toss = ArcisRNG::bool(); // MPC-generated randomness
let toss = ArcisRNG::bool();
(input.choice == toss).reveal()
}
```

> See [Arcis Primitives](https://docs.arcium.com/developers/arcis/primitives) for all randomness and cryptographic operations.

**How it works**:
Each Arcium node contributes local entropy. The MPC protocol combines these into a final value that no single node could predict before all contributed.

1. Arcium nodes each generate local random values
2. Nodes combine their randomness using secure multi-party computation
3. Final random value is deterministic given all inputs
4. **No single node can predict the result** before all contribute
5. **No subset of nodes can bias the outcome**: The MPC protocol guarantees unbiased randomness even with a dishonest majority—the outcome remains unpredictable as long as one node is honest
> See [Arcis Primitives](https://docs.arcium.com/developers/arcis/primitives) for all randomness and cryptographic operations.

### Stateless Design

Unlike Voting or Blackjack, Coinflip has **no game state account**:

- Receive encrypted player choice → Generate MPC random → Compare → Emit result
- Each flip is independent
- No persistent storage needed

When randomness generation itself is the primary feature, stateless design is simplest.

### When to Use This Pattern

Use MPC randomness (`ArcisRNG`) when:

- **No one should control outcome**: Lotteries, random drops, fair matchmaking
- **Platform can't be trusted**: House games where operator could cheat
- **Randomness is high-value**: Large prizes or critical game mechanics
Unlike Voting or Blackjack, Coinflip has no game state account. Each flip is independent -- receive encrypted choice, generate random, compare, emit result.
33 changes: 8 additions & 25 deletions ed25519/README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,27 @@
# Ed25519 Signatures - Confidential Key Management
# Ed25519 Signatures - Distributed Key Management

This example demonstrates Ed25519 signing and verification using distributed key management through multi-party computation. The private key is split across multiple nodes and never exists in a single location.
Ed25519 signing and verification using MPC. The private key is split across multiple nodes and never exists in a single location.

## How It Works

**Message Signing**: A message is sent to the Arcium network where MPC nodes collectively generate a valid Ed25519 signature using their key shares. The signature can be verified by anyone using the public key.
**Signing**: A message is sent to the Arcium network where MPC nodes collectively produce a valid Ed25519 signature using their key shares. The signature can be verified by anyone with the public key.

**Signature Verification with Confidential Public Key**: The verifying key (public key) is provided in encrypted form, and the signature is verified within MPC. Only the verification result (valid/invalid) is revealed to a designated observer.
**Verification with confidential public key**: The verifying key is provided encrypted, and verification runs inside MPC. Only the result (valid/invalid) is revealed to a designated observer.

## Running the Example

```bash
# Install dependencies
yarn install

# Build the program
arcium build

# Run tests
arcium test
```

The test suite demonstrates both signing and verification flows with the MPC-managed key.

## Technical Implementation
## Implementation

### MPC Signing

Arcium's `MXESigningKey` enables signing through distributed key shares:

```rust
pub fn sign_message(message: [u8; 5]) -> ArcisEd25519Signature {
let signature = MXESigningKey::sign(&message);
signature.reveal()
}
```

Each MPC node holds a share of the private key and executes a distributed signing protocol to produce a standard Ed25519 signature without reconstructing the complete key.
Each MPC node holds a key share and executes a distributed signing protocol to produce a standard Ed25519 signature without reconstructing the complete key.

> See [Arcis Primitives](https://docs.arcium.com/developers/arcis/primitives) for the full cryptographic API.
> [Arcis Primitives](https://docs.arcium.com/developers/arcis/primitives)

### Confidential Public Key Verification

Expand All @@ -56,4 +39,4 @@ pub fn verify_signature(
}
```

In some scenarios, revealing which public key is being verified could leak sensitive information (identity, organizational affiliations). This pattern enables verification without public key disclosure.
Revealing which public key is being verified could leak identity or organizational affiliations. This pattern enables verification without public key disclosure.
Loading
Loading