1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*

From: https://bitcointalk.org/index.php?topic=5162888.0

Welcome to our seeding event for Crash launching in Beta this week. We are seeding it similarly to
RHavar and others so it should be fairly straight forward.

To prove our fairness we have generated a chain of 10,000,000 SHA256 hashes where each hash is the
hash of the hexadecimal representation of the previous hash. The last hash in the chain is:
78a9757d3be42b74a3f70239078ad9317125fe9ee630d5bdada46de963e56752

The formula for generating the game result: Code: const gameHash = hashChain.pop()

const hmac = createHmac('sha256', gameHash);

// blockHash is the hash of bitcoin block 584,500

hmac.update(blockHash);

const hex = hmac.digest('hex').substr(0, 8); const int = parseInt(hex, 16);

// 0.01 will result in 1% house edge with a lowest crashpoint of 1

const crashpoint = Math.max(1, (2 ** 32 / (int + 1)) * (1 - 0.01))

blockHash used is Bitcoin block 584,500 which has not been mined at time of posting. Basically we
are using the hash of a future bitcoin block as a client seed so players can be certain that we did
not pick one in the house's favor. I'd appreciate it if someone could quote this post so this is
all set in stone.

Excited to show you all Crash very soon!

*/
use hex;
use hmac::{Hmac, Mac};
use sha2::digest::generic_array::typenum::*;
use sha2::digest::generic_array::GenericArray;
use sha2::{Digest, Sha256};
use std::fmt;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Hash {
    value: GenericArray<u8, U32>,
}

impl fmt::Display for Hash {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let bytes = &self.value[..];
        write!(f, "{}", hex::encode(bytes))
    }
}

impl Hash {
    pub fn new(value: GenericArray<u8, U32>) -> Hash {
        Hash { value }
    }
    fn digest(s: &str) -> Hash {
        Hash::new(Sha256::digest(s.as_bytes()))
    }
    pub fn from_hex(s: &str) -> Hash {
        let v = hex::decode(s).unwrap();
        Hash::new(GenericArray::clone_from_slice(&v[..]))
    }
    fn to_hex(&self) -> String {
        self.to_string()
    }
}

#[derive(Copy, Clone)]
pub struct Config {
    hash_chain_tip: Hash,
    block_hash: Hash,
    max_chain_length: usize,
}

impl Config {
    pub fn new(hash_chain_tip: Hash, block_hash: Hash, max_chain_length: usize) -> Config {
        Config {
            hash_chain_tip,
            block_hash,
            max_chain_length,
        }
    }
    pub fn for_stake() -> Config {
        // https://bitcointalk.org/index.php?topic=5162888.msg54134231#msg54134231
        // e.g. game:
        // https://stake.com/casino/games/crash?gameId=c4b5237d-1885-45ed-b1f4-6ade71e20991&modal=crash
        // Game # 1,126,614, hash: 5844bf329a6334074778ab8a5f0960e24f9eec43f83bbd98ac0a9f8bcd87184e
        let hash_chain_tip =
            Hash::from_hex("78a9757d3be42b74a3f70239078ad9317125fe9ee630d5bdada46de963e56752");
        let block_hash =
            Hash::from_hex("0000000000000000001b34dc6a1e86083f95500b096231436e9b25cbdd0075c4");
        let max_chain_length = 10_000_000;

        Config {
            hash_chain_tip,
            block_hash,
            max_chain_length,
        }
    }
}

#[derive(Copy, Clone)]
struct HashChain {
    hash: Hash,
}

impl HashChain {
    pub fn new(initial_hash: Hash) -> HashChain {
        HashChain { hash: initial_hash }
    }

    fn compute_next_hash(&self) -> Hash {
        Hash::digest(&self.hash.to_hex())
    }
}

impl std::iter::Iterator for HashChain {
    type Item = Hash;
    fn next(&mut self) -> Option<Self::Item> {
        // Some(self.next_float())
        let res = self.hash;
        self.hash = self.compute_next_hash();
        Some(res)
    }
}

type HmacSha256 = Hmac<Sha256>;

use std::convert::TryInto;

pub struct Outcome {
    crash_point: f64,
}

impl fmt::Display for Outcome {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Crash point: {}", self.crash_point)
    }
}

pub fn simulate(config: Config, game_hash: Hash) -> Outcome {
    let key = game_hash.to_hex();
    let input = config.block_hash.to_hex();
    let mut mac = HmacSha256::new_varkey(key.as_bytes())
        .expect("HMAC can take key of any size, never errors here");
    mac.input(input.as_bytes());
    let result = Hash::new(mac.result().code());

    // convert 4 first bytes of hash to u32
    let n = u32::from_be_bytes((&result.value[0..4]).try_into().unwrap());

    let n = n as f64;

    let crash_point = 1_f64.max((2_f64.powf(32.) / (n + 1.)) * (1. - 0.01));

    Outcome { crash_point }
}

// verify that the hash is really part of the hash chain
pub fn verify_hash(config: Config, game_hash: Hash) -> bool {
    let mut hash_chain = HashChain::new(game_hash);
    for _ in 0..config.max_chain_length {
        let h = hash_chain.next().unwrap();
        if h == config.hash_chain_tip {
            return true;
        }
    }
    return false;
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_crash_hash_chain() {
        let hash_chain: Vec<_> = HashChain::new(Hash::digest("testing")).take(3).collect();
        assert_eq!(
            Hash::digest("testing").to_string(),
            "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a90"
        );
        assert_eq!(
            hash_chain[0].to_string(),
            Hash::digest("testing").to_string()
        );
        assert_eq!(
            hash_chain[1].to_string(),
            Hash::digest(&hash_chain[0].to_hex()).to_string()
        );
        assert_eq!(
            hash_chain[2].to_string(),
            Hash::digest(&hash_chain[1].to_hex()).to_string()
        );
    }
    #[test]
    fn test_crash_simulate() {
        let hash_chain: Vec<_> = HashChain::new(Hash::digest("testing")).take(10).collect();
        let hash_chain_tip = *hash_chain.last().unwrap();
        let block_hash =
            Hash::from_hex("0000000000000000001b34dc6a1e86083f95500b096231436e9b25cbdd0075c4");
        let game_hash = hash_chain[2];
        let config = Config::new(hash_chain_tip, block_hash, hash_chain.len());
        let outcome = simulate(config, game_hash);
        println!("{}", game_hash);
        assert_eq!(outcome.crash_point, 1.5992214910117746);
        assert!(verify_hash(config, game_hash));
        let bad_game_hash =
            Hash::from_hex("deadbeefe7c270724bd4851c020d489257fa79a70e694a9b5099375464348698");
        assert!(!verify_hash(config, bad_game_hash), "bad_game_hash");
        let last_game_hash = hash_chain_tip;
        assert!(verify_hash(config, last_game_hash), "last_game_hash")
    }

    #[test]
    fn test_crash_simulate_2() {
        let hash_chain_tip =
            Hash::from_hex("0000000000000000001b34dc6a1e86083f95500b096231436e9b25cbdd0075c4");
        let block_hash =
            Hash::from_hex("0000000000000000001b34dc6a1e86083f95500b096231436e9b25cbdd0075c4");
        let config = Config::new(hash_chain_tip, block_hash, 0);
        let game_hash =
            Hash::from_hex("deadbeefe7c270724bd4851c020d489257fa79a70e694a9b5099375464348698");
        assert_eq!(simulate(config, game_hash).crash_point, 1.2897005203687084);
    }

    #[test]
    #[ignore] // too slow
    fn test_crash_verify() {
        let config = Config::for_stake();
        let game_hash =
            Hash::from_hex("5844bf329a6334074778ab8a5f0960e24f9eec43f83bbd98ac0a9f8bcd87184e");

        assert_eq!(simulate(config, game_hash).crash_point, 2.3522275811778033);
        assert!(verify_hash(config, game_hash));
    }
}