Skip to content

Commit

Permalink
Merge pull request #57 from JetonDAO/implement-smart-contracts
Browse files Browse the repository at this point in the history
Implement smart contracts
  • Loading branch information
arssly authored Oct 15, 2024
2 parents 22e48cd + 3fe96a3 commit 61243f4
Show file tree
Hide file tree
Showing 14 changed files with 2,410 additions and 1,517 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# jeton
# Jeton: A Decentralized and Trustless Poker Platform

Decentralized poker protocol enabling trustless, transparent, and community-governed gameplay – powered by JatonDAO.
Jeton is a decentralized poker platform designed to ensure fairness and transparency in online poker games. Built on the Aptos blockchain, Jeton eliminates the need for players to trust a central authority by leveraging zk-SNARKs (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge) and Elgamal encryption. These cryptographic techniques guarantee that the cards are shuffled, encrypted, and dealt fairly without revealing any information to players or the platform itself.

The platform utilizes Elgamal encryption on the JubJub elliptic curve to securely encrypt cards and zk-SNARK circuits to verify both the shuffle and decryption processes. Each player participates in shuffling the deck and generating decryption shares, ensuring no individual player or entity can manipulate the outcome. Smart contracts on the Aptos blockchain handle the game logic and verify cryptographic proofs, providing a fully decentralized and tamper-proof environment for online poker.

For a more detailed explanation of the algorithms, security considerations, and cryptographic methods used in Jeton, you can read the [full project overview](project-overview.md).

## What's inside?

This Turborepo includes the following packages/apps:

### Apps and Packages

- `web`: a [Next.js](https://nextjs.org/) app
- `web`: the web application of the jeton protocol, implemented in [Next.js](https://nextjs.org/)
- `@jeton/zk-deck`: a package containing Move, TypeScript, and Circom code, for implementing zero knowledge based playing decks
- `@jeton/smart-contracts`: a Move package responsible for on chain game logic
- `@jeton/ts-sdk`:
- `@jeton/ui`: a React component library shared by `web` application
- `@jeton/tailwindcss-config`:
- `@jeton/typescript-config`: `tsconfig.json`s used throughout the monorepo
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).

Expand Down
5 changes: 3 additions & 2 deletions packages/smart-contracts/Move.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ name = "Jeton"
version = "0.0.0"

[addresses]
std = "0x1"
aptos_framework = "0x1"
zk_deck = "0x05a973c774644f1d27beab9308fc05d4a35d49e5108411fff5dad3105190178f"
aptos_std = "0x1"
std = "0x1"
zk_deck = "0x2550c62d611b5f27ff81a8839b32b5b540cbca2d692a7cf6cdd6127077b31b5d"
jeton = "_"

[dev-addresses]
Expand Down
2 changes: 1 addition & 1 deletion packages/smart-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"move:build": "aptos move compile --move-2 --named-addresses jeton=default",
"move:test": "aptos move test --move-2 --named-addresses jeton=default",
"move:publish": "aptos move create-resource-account-and-publish-package --address-name jeton --seed jeton --move-2 --assume-yes"
"move:publish": "aptos move deploy-object --address-name jeton --move-2 --assume-yes"
},
"devDependencies": {
"@aptos-labs/aptos-cli": "^0.2.0"
Expand Down
77 changes: 77 additions & 0 deletions packages/smart-contracts/sources/chip_stack.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module jeton::chip_stack {
use aptos_framework::event;
use std::signer;

const EINSUFFICIENT_BALANCE: u64 = 1;
const EDESTROY_NON_ZERO: u64 = 1;

struct ChipStack (u64) has store;

package fun zero(): ChipStack {
ChipStack(0)
}

#[event]
struct ChipStackFrom has drop, store {
addr: address,
amount: u64,
}

package fun from(sender: &signer, amount: u64): ChipStack {
event::emit(ChipStackFrom{ addr: signer::address_of(sender), amount, });
ChipStack(amount)
}

package fun extract(self: &mut ChipStack, amount: u64): ChipStack {
if (self.0 < amount) {
abort EINSUFFICIENT_BALANCE
};
self.0 = self.0 - amount;
ChipStack(amount)
}

package fun extract_all(self: &mut ChipStack): ChipStack {
let amount = self.0;
self.0 = 0;
ChipStack(amount)
}

package fun merge(self: ChipStack, to: &mut ChipStack) {
let ChipStack(amount) = self;
to.0 = to.0 + amount;
}

package fun destroy_zero(self: ChipStack) {
let ChipStack(amount) = self;
if (amount != 0) {
abort EDESTROY_NON_ZERO
};
}

#[event]
struct ChipStackTo has drop, store {
addr: address,
amount: u64,
}

package fun to(self: ChipStack, receiver: address) {
let ChipStack(amount) = self;
event::emit(ChipStackTo{ addr: receiver, amount, });
}

package fun transfer(self: &mut ChipStack, to: &mut ChipStack, amount: u64) {
self.extract(amount).merge(to)
}

package fun transfer_all(self: &mut ChipStack, to: &mut ChipStack) {
self.extract(self.0).merge(to)
}

package fun is_zero(self: &ChipStack): bool {
self.0 == 0
}

package fun balance(self: &ChipStack): u64 {
self.0
}
}
243 changes: 243 additions & 0 deletions packages/smart-contracts/sources/holdem_hand.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
module jeton::holdem_hand {
use aptos_std::comparator;
use std::option;
use std::option::Option;
use std::vector;

fun sort_cards(cards: &mut vector<u8>) {
let num_cards = cards.length();
for (i in 0..num_cards) {
for (j in (i+1)..num_cards) {
if (cards[i] < cards[j]) {
cards.swap(i, j);
};
};
};
}

fun find_straight(values: &vector<u8>): Option<u8> {
let num_cards = values.length();
let last_value = values[0];
let num_straights = 1;
for (i in 1..num_cards) {
let value = values[i];
if (value == last_value) {
continue;
};
if (last_value - value == 1) {
num_straights = num_straights + 1;
if (num_straights == 5) {
return option::some(value + 4)
};
} else {
num_straights = 1;
};
last_value = value;
};
if (num_straights == 4 && last_value == 0 && values[0] == 12) {
return option::some(3)
};
option::none()
}

fun evaluate_hand(cards: vector<u8>): vector<u8> {
sort_cards(&mut cards);
let num_cards = cards.length();
let four_of_kind = option::none<u8>();
let three_of_kind = option::none<u8>();
let first_pair = option::none<u8>();
let second_pair = option::none<u8>();
let kickers = vector::empty<u8>();
let values = vector::empty<u8>();
let clubs = vector::empty<u8>();
let diamonds = vector::empty<u8>();
let hearts = vector::empty<u8>();
let spades = vector::empty<u8>();

let last_value = cards[0] / 4;
let last_value_count = 1;
values.push_back(last_value);
if (cards[0] % 4 == 0) {
clubs.push_back(last_value);
} else if (cards[0] % 4 == 1) {
diamonds.push_back(last_value);
} else if (cards[0] % 4 == 2) {
hearts.push_back(last_value);
} else {
spades.push_back(last_value);
};
for (i in 1..(num_cards+1)) {
if (i < num_cards && cards[i] / 4 == last_value) {
last_value_count = last_value_count + 1;
continue;
};
if (last_value_count == 4 && four_of_kind.is_none()) {
four_of_kind.fill(last_value);
} else if (last_value_count >= 3 && three_of_kind.is_none()) {
three_of_kind.fill(last_value);
} else if (last_value_count >= 2 && first_pair.is_none()) {
first_pair.fill(last_value);
} else if (last_value_count >= 2 && second_pair.is_none()) {
second_pair.fill(last_value);
} else {
kickers.push_back(last_value);
};
if (i < num_cards) {
last_value = cards[i] / 4;
last_value_count = 1;
values.push_back(last_value);
if (cards[0] % 4 == 0) {
clubs.push_back(last_value);
} else if (cards[0] % 4 == 1) {
diamonds.push_back(last_value);
} else if (cards[0] % 4 == 2) {
hearts.push_back(last_value);
} else {
spades.push_back(last_value);
}
};
};

// Straight Flush
let clubs_straight = find_straight(&clubs);
if (clubs_straight.is_some()) {
return vector[8, clubs_straight.destroy_some()]
};
let diamonds_straight = find_straight(&diamonds);
if (diamonds_straight.is_some()) {
return vector[8, diamonds_straight.destroy_some()]
};
let hearts_straight = find_straight(&hearts);
if (hearts_straight.is_some()) {
return vector[8, hearts_straight.destroy_some()]
};
let spades_straight = find_straight(&spades);
if (spades_straight.is_some()) {
return vector[8, spades_straight.destroy_some()]
};

// Four of a Kind
if (four_of_kind.is_some()) {
return vector[7, four_of_kind.destroy_some()]
};

// Full House
if (three_of_kind.is_some() && first_pair.is_some()) {
return vector[6, three_of_kind.destroy_some(), first_pair.destroy_some()]
};

// Flush
if (clubs.length() >= 5) {
let rank = vector[5];
rank.append(clubs.slice(0, 5));
return rank;
};
if (diamonds.length() >= 5) {
let rank = vector[5];
rank.append(diamonds.slice(0, 5));
return rank;
};
if (hearts.length() >= 5) {
let rank = vector[5];
rank.append(hearts.slice(0, 5));
return rank;
};
if (spades.length() >= 5) {
let rank = vector[5];
rank.append(spades.slice(0, 5));
return rank;
};

// Straight
let values_straight = find_straight(&values);
if (values_straight.is_some()) {
return vector[4, values_straight.destroy_some()]
};

// Three of a Kind
if (three_of_kind.is_some()) {
let rank = vector[3, three_of_kind.destroy_some()];
rank.append(kickers.slice(0, 2));
return rank
};

if (first_pair.is_some() && second_pair.is_some()) {
return vector[
2,
first_pair.destroy_some(),
second_pair.destroy_some(),
kickers.pop_back(),
]
};

if (first_pair.is_some()) {
let rank = vector[1, first_pair.destroy_some()];
rank.append(kickers.slice(0, 3));
return rank
};

let rank = vector[0];
rank.append(kickers.slice(0, 5));
rank
}

fun sort_hands(
private_cards: &vector<u8>,
public_cards: &vector<u8>,
is_foldeds: &vector<bool>,
): vector<vector<u8>> {
let num_players = private_cards.length() / 2;
let ranks = vector::empty<vector<u8>>();
let classes = vector::empty<vector<u8>>();
let foldeds = vector::empty<u8>();
for (i in 0..num_players) {
if (is_foldeds[i]) {
foldeds.push_back(i as u8);
continue
};
let cards = *public_cards;
cards.append(private_cards.slice((2*i) as u64, (2*i+2) as u64));
let rank = evaluate_hand(cards);

let num_classes = classes.length();
for (j in 0..num_classes) {
let compare = comparator::compare_u8_vector(copy rank, ranks[j]);
if (comparator::is_equal(&compare)) {
classes[i].push_back(i as u8);
break
};
if (comparator::is_greater_than(&compare)) {
classes.insert(j, vector[i as u8]);
ranks.insert(j, rank);
break
};
if (i == num_classes - 1) {
classes.push_back(vector[i as u8]);
ranks.push_back(rank);
};
};
};
classes.push_back(foldeds);
classes
}

package fun calculate_winning_amounts(
private_cards: &vector<u8>,
public_cards: &vector<u8>,
bet_amounts: &vector<u64>,
is_foldeds: &vector<bool>,
): vector<u64> {
let num_players = bet_amounts.length();
let pot_amount = 0;
for (i in 0..num_players) {
pot_amount = pot_amount + bet_amounts[i];
};
let winning_amounts = vector::empty<u64>();
for (i in 0..num_players) {
winning_amounts.push_back(0);
};
let sorted = sort_hands(private_cards, public_cards, is_foldeds);
winning_amounts[sorted[0][0] as u64] = pot_amount;
winning_amounts
}
}
Loading

0 comments on commit 61243f4

Please sign in to comment.