Skip to main content

Crate ziffle

Crate ziffle 

Source
Expand description

Mental poker shuffle protocol using zero-knowledge proofs.

ziffle implements a mental poker protocol where multiple players can collaboratively shuffle a deck of cards without any player learning the order, and then selectively reveal individual cards.

The protocol uses Bayer-Groth 2012 shuffle proofs to ensure that each shuffle is performed correctly without revealing the permutation.

ziffle is a #[no_std] crate.

§⚠️ Security Warning

This code has not been independently audited. It is experimental software provided as-is without any warranties. While the implementation follows the Bayer-Groth 2012 specification, it may contain bugs or vulnerabilities.

DO NOT use this library to play for non-trivial amounts of money or in any high-stakes scenario. Use at your own risk.

§Main Entry Point

The Shuffle struct is the primary interface for using this library. Create an instance with Shuffle::<N>::default() where N is the number of cards in your deck.

§Example: Three-Player Poker Game

use ziffle::{Shuffle, AggregatePublicKey, AggregateRevealToken};

// Create a standard 52-card deck
let shuffle = Shuffle::<52>::default();
let mut rng = ark_std::test_rng(); // DO NOT USE IN PRODUCTION
let ctx = b"poker_game_session_123";

// Three players generate their keys and prove ownership
let (alice_sk, alice_pk, alice_proof) = shuffle.keygen(&mut rng, ctx);
let (bob_sk, bob_pk, bob_proof) = shuffle.keygen(&mut rng, ctx);
let (carol_sk, carol_pk, carol_proof) = shuffle.keygen(&mut rng, ctx);

// Each player verifies others' key ownership proofs
let alice_vpk = alice_proof.verify(alice_pk, ctx).unwrap();
let bob_vpk = bob_proof.verify(bob_pk, ctx).unwrap();
let carol_vpk = carol_proof.verify(carol_pk, ctx).unwrap();

// Create aggregate public key from all verified keys
let apk = AggregatePublicKey::new(&[alice_vpk, bob_vpk, carol_vpk]);

// Alice performs the initial shuffle
let (alice_deck, alice_proof) = shuffle.shuffle_initial_deck(&mut rng, apk, ctx);
let alice_vdeck = shuffle
    .verify_initial_shuffle(apk, alice_deck, alice_proof, ctx)
    .expect("Alice's shuffle should be valid");

// Bob shuffles Alice's deck
let (bob_deck, bob_proof) = shuffle.shuffle_deck(&mut rng, apk, &alice_vdeck, ctx);
let bob_vdeck = shuffle
    .verify_shuffle(apk, &alice_vdeck, bob_deck, bob_proof, ctx)
    .expect("Bob's shuffle should be valid");

// Carol shuffles Bob's deck
let (final_deck, carol_proof) = shuffle.shuffle_deck(&mut rng, apk, &bob_vdeck, ctx);
let final_vdeck = shuffle
    .verify_shuffle(apk, &bob_vdeck, final_deck, carol_proof, ctx)
    .expect("Carol's shuffle should be valid");

// Now the deck is fully shuffled and encrypted. Let's reveal the first card.
let first_card = final_vdeck.get(0).unwrap();

// Each player creates a reveal token for the first card
let (alice_token, alice_token_proof) =
    first_card.reveal_token(&mut rng, &alice_sk, alice_pk, ctx);
let (bob_token, bob_token_proof) =
    first_card.reveal_token(&mut rng, &bob_sk, bob_pk, ctx);
let (carol_token, carol_token_proof) =
    first_card.reveal_token(&mut rng, &carol_sk, carol_pk, ctx);

// All players verify each other's reveal tokens
let alice_vtoken = alice_token_proof
    .verify(alice_vpk, alice_token, first_card, ctx)
    .expect("Alice's token should be valid");
let bob_vtoken = bob_token_proof
    .verify(bob_vpk, bob_token, first_card, ctx)
    .expect("Bob's token should be valid");
let carol_vtoken = carol_token_proof
    .verify(carol_vpk, carol_token, first_card, ctx)
    .expect("Carol's token should be valid");

// Aggregate the verified tokens to decrypt the card
let aggregate_token = AggregateRevealToken::new(&[alice_vtoken, bob_vtoken, carol_vtoken]);

// Reveal the card's index in the original deck (0-51)
let card_index = shuffle
    .reveal_card(aggregate_token, first_card)
    .expect("Card should be revealed successfully");

println!("First card is at index: {}", card_index);
assert!(card_index < 52);

§Serialized Sizes

The following table shows the serialized sizes of types that need to be transmitted over the network in a typical mental poker protocol. All sizes use compressed canonical serialization from arkworks.

TypeSizeNotes
PublicKey33 bytesOne per player at setup
OwnershipProof65 bytesOne per player at setup
MaskedDeck<52>3,432 bytes66 bytes per card (33 × 2)
ShuffleProof<52>5,547 bytesOne per shuffle operation
RevealToken33 bytesOne per player per revealed card
RevealTokenProof98 bytesOne per player per revealed card

Structs§

AggregatePublicKey
Aggregate public key combining all players’ public keys.
AggregateRevealToken
Aggregate reveal token combining all players’ reveal tokens.
MaskedCard
An encrypted card from the deck.
MaskedDeck
A deck of N encrypted cards.
OwnershipProof
Zero-knowledge proof of secret key ownership.
PublicKey
Public key for a player, derived from their secret key.
RevealToken
Decryption share for a specific card from one player.
RevealTokenProof
Zero-knowledge proof for a reveal token using a DLEQ (Discrete Log Equality) proof.
SecretKey
Secret key for a player. Memory is automatically wiped on drop for security.
Shuffle
Mental poker shuffle protocol for N cards using Bayer-Groth 2012 shuffle proofs.
ShuffleProof
BayerGroth 2012 (BG12) Efficient Zero-Knowledge Argument for Correctness of a Shuffle
Verified
Wrapper type indicating that a value has been cryptographically verified.