Skip to content

Commit 79fa34f

Browse files
committed
2-player dummy lottery
Signed-off-by: Dikshant <[email protected]>
1 parent ef2fc30 commit 79fa34f

File tree

12 files changed

+1381
-1
lines changed

12 files changed

+1381
-1
lines changed

Cargo.lock

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["arkive-core", "arkive-cli"]
2+
members = ["arkive-core", "arkive-cli", "arkive-lottery", "coinflip-cli"]
33
resolver = "2"
44

55
[workspace.dependencies]

arkive-lottery/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "arkive-lottery"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Zero-collateral lottery implementation"
6+
authors = ["Dikshant <[email protected]>"]
7+
license = "MIT OR Apache-2.0"
8+
9+
[dependencies]
10+
arkive-core = { path = "../arkive-core" }
11+
bitcoin = { workspace = true }
12+
serde = { workspace = true, features = ["derive"] }
13+
serde_json = { workspace = true }
14+
tokio = { workspace = true }
15+
anyhow = { workspace = true }
16+
thiserror = { workspace = true }
17+
uuid = { workspace = true, features = ["v4", "serde"] }
18+
chrono = { workspace = true, features = ["serde"] }
19+
sha2 = "0.10"
20+
hex = "0.4"
21+
rand = "0.8"
22+
tracing = { workspace = true }
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
pub mod scheme;
2+
3+
pub use scheme::{Commitment, CommitmentScheme};
4+
5+
use rand::RngCore;
6+
use serde::{Deserialize, Serialize};
7+
use sha2::{Digest, Sha256};
8+
9+
/// Hash based commitment impl
10+
#[derive(Debug, Clone, Serialize, Deserialize)]
11+
pub struct HashCommitment {
12+
hash: Vec<u8>,
13+
#[serde(skip)]
14+
secret: Option<Vec<u8>>,
15+
}
16+
17+
impl HashCommitment {
18+
pub fn new(secret: Vec<u8>) -> Self {
19+
let mut hasher = Sha256::new();
20+
hasher.update(&secret);
21+
let hash = hasher.finalize().to_vec();
22+
23+
Self {
24+
hash,
25+
secret: Some(secret),
26+
}
27+
}
28+
29+
pub fn from_hash(hash: Vec<u8>) -> Self {
30+
Self { hash, secret: None }
31+
}
32+
33+
pub fn hash(&self) -> &[u8] {
34+
&self.hash
35+
}
36+
37+
pub fn verify(&self, secret: &[u8]) -> bool {
38+
let mut hasher = Sha256::new();
39+
hasher.update(secret);
40+
let computed_hash = hasher.finalize();
41+
computed_hash.as_slice() == self.hash
42+
}
43+
44+
pub fn reveal(self) -> Option<Vec<u8>> {
45+
self.secret
46+
}
47+
}
48+
49+
/// Rnd secret for commitment
50+
pub fn generate_secret() -> Vec<u8> {
51+
let mut secret = vec![0u8; 32];
52+
rand::thread_rng().fill_bytes(&mut secret);
53+
secret
54+
}
55+
56+
/// Winner from two secrets using XOR
57+
pub fn determine_winner(secret1: &[u8], secret2: &[u8]) -> bool {
58+
let combined = secret1
59+
.iter()
60+
.zip(secret2.iter())
61+
.map(|(a, b)| a ^ b)
62+
.collect::<Vec<u8>>();
63+
64+
let winner_bit = combined.iter().fold(0u8, |acc, &byte| acc ^ byte) & 1;
65+
winner_bit == 0 // true = player1 wins, false = player2 wins
66+
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
72+
#[test]
73+
fn test_commitment_scheme() {
74+
let secret = generate_secret();
75+
let commitment = HashCommitment::new(secret.clone());
76+
77+
assert!(commitment.verify(&secret));
78+
assert!(!commitment.verify(b"wrong secret"));
79+
}
80+
81+
#[test]
82+
fn test_winner_determination() {
83+
let secret1 = vec![0x00, 0x00, 0x00, 0x00];
84+
let secret2 = vec![0x00, 0x00, 0x00, 0x01];
85+
86+
let winner = determine_winner(&secret1, &secret2);
87+
assert!(!winner);
88+
}
89+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use crate::Result;
2+
use chrono::{DateTime, Utc};
3+
use serde::{Deserialize, Serialize};
4+
use uuid::Uuid;
5+
6+
/// Trait for commitment schemes
7+
pub trait CommitmentScheme {
8+
type Secret;
9+
type Commitment;
10+
11+
fn commit(secret: Self::Secret) -> Self::Commitment;
12+
fn verify(commitment: &Self::Commitment, secret: &Self::Secret) -> bool;
13+
}
14+
15+
/// A commitment in the lottery protocol
16+
#[derive(Debug, Clone, Serialize, Deserialize)]
17+
pub struct Commitment {
18+
pub hash: Vec<u8>,
19+
pub player_id: Uuid,
20+
pub timestamp: DateTime<Utc>,
21+
pub nonce: Vec<u8>, // to prevent replay
22+
}
23+
24+
impl Commitment {
25+
pub fn new(hash: Vec<u8>, player_id: Uuid) -> Self {
26+
let mut nonce = vec![0u8; 16];
27+
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce);
28+
29+
Self {
30+
hash,
31+
player_id,
32+
timestamp: Utc::now(),
33+
nonce,
34+
}
35+
}
36+
37+
pub fn verify_secret(&self, secret: &[u8]) -> Result<bool> {
38+
use sha2::{Digest, Sha256};
39+
40+
let mut hasher = Sha256::new();
41+
hasher.update(secret);
42+
hasher.update(&self.nonce);
43+
let computed_hash = hasher.finalize();
44+
45+
Ok(computed_hash.as_slice() == self.hash)
46+
}
47+
48+
pub fn create_with_secret(secret: &[u8], player_id: Uuid) -> Self {
49+
use sha2::{Digest, Sha256};
50+
51+
let mut nonce = vec![0u8; 16];
52+
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce);
53+
54+
let mut hasher = Sha256::new();
55+
hasher.update(secret);
56+
hasher.update(&nonce);
57+
let hash = hasher.finalize().to_vec();
58+
59+
Self {
60+
hash,
61+
player_id,
62+
timestamp: Utc::now(),
63+
nonce,
64+
}
65+
}
66+
}

arkive-lottery/src/error.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use thiserror::Error;
2+
use uuid::Uuid;
3+
4+
pub type Result<T> = std::result::Result<T, LotteryError>;
5+
6+
#[derive(Error, Debug)]
7+
pub enum LotteryError {
8+
#[error("Arkive core error: {0}")]
9+
ArkiveCore(#[from] arkive_core::ArkiveError),
10+
11+
#[error("Invalid game state: {0}")]
12+
InvalidState(String),
13+
14+
#[error("Player not found: {0}")]
15+
PlayerNotFound(Uuid),
16+
17+
#[error("Game is full")]
18+
GameFull,
19+
20+
#[error("Game not ready")]
21+
GameNotReady,
22+
23+
#[error("Commitment already submitted")]
24+
CommitmentAlreadySubmitted,
25+
26+
#[error("Commitment not revealed for player: {0}")]
27+
CommitmentNotRevealed(Uuid),
28+
29+
#[error("Invalid commitment")]
30+
InvalidCommitment,
31+
32+
#[error("Timeout expired")]
33+
TimeoutExpired,
34+
35+
#[error("Cryptographic error: {0}")]
36+
Crypto(String),
37+
38+
#[error("Serialization error: {0}")]
39+
Serialization(#[from] serde_json::Error),
40+
41+
#[error("Internal error: {0}")]
42+
Internal(String),
43+
}

0 commit comments

Comments
 (0)