@actuallyfair/verifier 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,12 +14,61 @@ import {
14
14
  import { bytesToHex } from "@noble/hashes/utils";
15
15
  import { CrashDice } from "./generated/context/crash-dice";
16
16
  import { MultiRoulette } from "./generated/context/multi-roulette";
17
- import { getDuelPlinkoPayoutsFromEnum } from "./duel-plinko-payouts";
18
- import { Plinko } from "./generated/context/plinko";
19
17
 
20
- export function computeFairCoinTossResult(sig: Uint8Array) {
21
- // We're going to hash the signature just to really be sure its fairly distributed
22
- const hash = sha256(sig);
18
+ export type CompressedSeed = number;
19
+ export type RandomSource = Uint8Array;
20
+
21
+ export function splitHelixHash(hash: Uint8Array): {
22
+ lhs: Uint8Array;
23
+ rhs: Uint8Array;
24
+ } {
25
+ if (hash.length % 2 !== 0) {
26
+ throw new Error("Helix hash input must have an even number of bytes.");
27
+ }
28
+
29
+ const mid = hash.length >>> 1;
30
+ return {
31
+ lhs: hash.subarray(0, mid),
32
+ rhs: hash.subarray(mid),
33
+ };
34
+ }
35
+
36
+ export function computeCompressedSeed(
37
+ clientSeed: string,
38
+ lhsHash: Uint8Array
39
+ ): CompressedSeed {
40
+ const seedHash = hmacSha256(lhsHash, clientSeed);
41
+ return u32FromHash(seedHash);
42
+ }
43
+
44
+ export function computeCompressedSeedFromGameHash(
45
+ clientSeed: string,
46
+ gameHash: Uint8Array
47
+ ): CompressedSeed {
48
+ const { lhs } = splitHelixHash(gameHash);
49
+ return computeCompressedSeed(clientSeed, lhs);
50
+ }
51
+
52
+ export function computeRandomSourceFromCompressedSeed(
53
+ compressedSeed: CompressedSeed,
54
+ rhsHash: Uint8Array
55
+ ): RandomSource {
56
+ const seedBytes = u32ToBytes(compressedSeed);
57
+ return hmacSha256Bytes(rhsHash, seedBytes);
58
+ }
59
+
60
+ export function computeRandomSource(
61
+ clientSeed: string,
62
+ gameHash: Uint8Array
63
+ ): RandomSource {
64
+ const { lhs, rhs } = splitHelixHash(gameHash);
65
+ const compressedSeed = computeCompressedSeed(clientSeed, lhs);
66
+ return computeRandomSourceFromCompressedSeed(compressedSeed, rhs);
67
+ }
68
+
69
+ export function computeFairCoinTossResult(randomSource: RandomSource) {
70
+ // We're going to hash the random source just to really be sure its fairly distributed
71
+ const hash = sha256(randomSource);
23
72
  const result = hash[0] % 2;
24
73
  if (result == 0) {
25
74
  return FairCoinToss_Choice.HEADS;
@@ -28,8 +77,11 @@ export function computeFairCoinTossResult(sig: Uint8Array) {
28
77
  }
29
78
  }
30
79
 
31
- export function computeFairCoinTossOutcome(sig: Uint8Array, w: FairCoinToss) {
32
- const result = computeFairCoinTossResult(sig);
80
+ export function computeFairCoinTossOutcome(
81
+ randomSource: RandomSource,
82
+ w: FairCoinToss
83
+ ) {
84
+ const result = computeFairCoinTossResult(randomSource);
33
85
 
34
86
  const win = w.playerChoice === result;
35
87
 
@@ -41,11 +93,11 @@ export function computeFairCoinTossOutcome(sig: Uint8Array, w: FairCoinToss) {
41
93
  };
42
94
  }
43
95
 
44
- function doComputeCrashResult(hash: Uint8Array, houseEdge: number) {
96
+ function doComputeCrashResult(randomSource: RandomSource, houseEdge: number) {
45
97
  const nBits = 52;
46
- const hashHex = bytesToHex(hash);
98
+ const randomSourceHex = bytesToHex(randomSource);
47
99
 
48
- const seed = hashHex.slice(0, nBits / 4);
100
+ const seed = randomSourceHex.slice(0, nBits / 4);
49
101
  const r = Number.parseInt(seed, 16);
50
102
 
51
103
  let X = r / 2 ** nBits; // uniformly distributed in [0; 1)
@@ -61,11 +113,11 @@ function doComputeCrashResult(hash: Uint8Array, houseEdge: number) {
61
113
  }
62
114
 
63
115
  export function computeCrashResult(
64
- vxSignature: Uint8Array,
116
+ hash: Uint8Array,
65
117
  gameHash: Uint8Array, // This is the hash of the next from the hash chain
66
118
  houseEdge: number = 0
67
119
  ) {
68
- return doComputeCrashResult(hmac(sha256, vxSignature, gameHash), houseEdge);
120
+ return doComputeCrashResult(hmac(sha256, hash, gameHash), houseEdge);
69
121
  }
70
122
 
71
123
  export type CrashDiceOutcome = {
@@ -74,17 +126,25 @@ export type CrashDiceOutcome = {
74
126
  win: boolean;
75
127
  };
76
128
 
129
+ export function computeCrashDiceResultFromRandomSource(
130
+ randomSource: RandomSource
131
+ ) {
132
+ const normalized = u32FromHash(randomSource);
133
+ const max = 2 ** 32;
134
+ const multiplierTimes100 = Math.floor((100 * max) / (max - normalized));
135
+ return multiplierTimes100 / 100;
136
+ }
137
+
77
138
  export function computeCrashDiceResult(hash: Uint8Array, clientSeed: string) {
78
- const rollHash = hmacSha256(hash, clientSeed);
79
- return multiplierFromHash(rollHash);
139
+ const randomSource = computeRandomSource(clientSeed, hash);
140
+ return computeCrashDiceResultFromRandomSource(randomSource);
80
141
  }
81
142
 
82
- export function computeCrashDiceOutcome(
83
- hash: Uint8Array,
84
- clientSeed: string,
143
+ export function computeCrashDiceOutcomeFromRandomSource(
144
+ randomSource: RandomSource,
85
145
  bet: CrashDice
86
146
  ): CrashDiceOutcome {
87
- const multiplier = computeCrashDiceResult(hash, clientSeed);
147
+ const multiplier = computeCrashDiceResultFromRandomSource(randomSource);
88
148
  const target = bet.target;
89
149
 
90
150
  return {
@@ -94,15 +154,24 @@ export function computeCrashDiceOutcome(
94
154
  };
95
155
  }
96
156
 
157
+ export function computeCrashDiceOutcome(
158
+ hash: Uint8Array,
159
+ clientSeed: string,
160
+ bet: CrashDice
161
+ ): CrashDiceOutcome {
162
+ const randomSource = computeRandomSource(clientSeed, hash);
163
+ return computeCrashDiceOutcomeFromRandomSource(randomSource, bet);
164
+ }
165
+
97
166
  // returns the index of which roulette outcome was picked
98
167
  export function computeMultiRouletteResult(
99
- vxSignature: Uint8Array,
168
+ randomSource: RandomSource,
100
169
  bet: MultiRoulette
101
170
  ) {
102
- const hash = sha256(vxSignature);
171
+ const seedHash = sha256(randomSource);
103
172
 
104
173
  const nBits = 52;
105
- const hashHex = bytesToHex(hash);
174
+ const hashHex = bytesToHex(seedHash);
106
175
  const seed = hashHex.slice(0, nBits / 4);
107
176
  const n = Number.parseInt(seed, 16);
108
177
 
@@ -119,7 +188,7 @@ export function computeMultiRouletteResult(
119
188
  }
120
189
 
121
190
  export function computeMineLocations(
122
- vxSignature: Uint8Array,
191
+ randomSource: RandomSource,
123
192
  revealedCells: Set<number>, // tiles we know are safe
124
193
  cells: number, // how many cells in total
125
194
  mines: number // how many mines there are going to be in total
@@ -136,7 +205,7 @@ export function computeMineLocations(
136
205
  break;
137
206
  }
138
207
 
139
- let mineIndex = Number(bytesToNumberBE(vxSignature) % BigInt(cellsLeft));
208
+ let mineIndex = Number(bytesToNumberBE(randomSource) % BigInt(cellsLeft));
140
209
  let adjustedIndex = 0;
141
210
 
142
211
  for (let i = 0; i < cells; i++) {
@@ -211,7 +280,7 @@ export function computePinkoPossibilityIndexFromPath(path: PlinkoPath) {
211
280
  // return a path (saying 'L' or 'R', where 'L' means go left, and 'R' means going right)
212
281
  // of possibilities-1 length
213
282
  export function computePlinkoPath(
214
- vxSignature: Uint8Array,
283
+ randomSource: RandomSource,
215
284
  possibilities: number
216
285
  ): PlinkoPath {
217
286
  if (
@@ -221,9 +290,9 @@ export function computePlinkoPath(
221
290
  ) {
222
291
  throw new Error("invalid possibilities ");
223
292
  }
224
- const hash = sha256(vxSignature);
293
+ const pathHash = sha256(randomSource);
225
294
 
226
- let n = bytesToNumberBE(hash);
295
+ let n = bytesToNumberBE(pathHash);
227
296
 
228
297
  let ret: ("L" | "R")[] = [];
229
298
  for (let i = 0; i < possibilities - 1; i++) {
@@ -259,12 +328,12 @@ export function computePlinkoPascalsProbabilities(rowNumber: number) {
259
328
  return lastRow.map((v) => v / sum);
260
329
  }
261
330
 
262
- export function computePlinkoHouseEdge(possibilities: number[]) {
263
- const odds = computePlinkoPascalsProbabilities(possibilities.length);
331
+ export function computePlinkoHouseEdge(payouts: number[]) {
332
+ const odds = computePlinkoPascalsProbabilities(payouts.length);
264
333
 
265
334
  let ev = 1; // you start off by betting everything
266
- for (let i = 0; i < possibilities.length; i++) {
267
- ev -= possibilities[i] * odds[i];
335
+ for (let i = 0; i < payouts.length; i++) {
336
+ ev -= payouts[i] * odds[i];
268
337
  }
269
338
  return ev;
270
339
  }
@@ -272,44 +341,34 @@ export function computePlinkoHouseEdge(possibilities: number[]) {
272
341
  export type PlinkoResult = {
273
342
  slot: number;
274
343
  multiplier: number;
275
- win: boolean;
276
344
  };
277
345
 
278
- export function computePlinkoResult(
279
- hash: Uint8Array,
280
- clientSeed: string,
281
- bet: Plinko
346
+ export function computePlinkoResultFromRandomSource(
347
+ randomSource: RandomSource,
348
+ payouts: number[]
282
349
  ): PlinkoResult {
283
- const possibilities = resolvePlinkoPayouts(bet);
284
- if (possibilities.length < 2) {
350
+ if (payouts.length < 2) {
285
351
  throw new Error("invalid possibilities ");
286
352
  }
287
353
 
288
- const probabilities = computePlinkoPascalsProbabilities(possibilities.length);
289
- const rollHash = hmacSha256(hash, clientSeed);
290
- const roll = uniformFromHash(rollHash);
354
+ const probabilities = computePlinkoPascalsProbabilities(payouts.length);
355
+ const roll = uniformFromHash(randomSource);
291
356
  const slot = pickSlot(probabilities, roll);
292
- const multiplier = possibilities[slot] ?? 0;
357
+ const multiplier = payouts[slot] ?? 0;
293
358
 
294
359
  return {
295
360
  slot,
296
361
  multiplier,
297
- win: multiplier >= 1,
298
362
  };
299
363
  }
300
364
 
301
- function resolvePlinkoPayouts(bet: Plinko): number[] {
302
- const payouts = bet.payouts;
303
- if (!payouts) {
304
- return [];
305
- }
306
- if (payouts.duel) {
307
- return getDuelPlinkoPayoutsFromEnum(payouts.duel.rows, payouts.duel.risk);
308
- }
309
- if (payouts.custom) {
310
- return payouts.custom.possibilities;
311
- }
312
- return [];
365
+ export function computePlinkoResult(
366
+ hash: Uint8Array,
367
+ clientSeed: string,
368
+ payouts: number[]
369
+ ): PlinkoResult {
370
+ const randomSource = computeRandomSource(clientSeed, hash);
371
+ return computePlinkoResultFromRandomSource(randomSource, payouts);
313
372
  }
314
373
 
315
374
  function hmacSha256(key: Uint8Array, message: string): Uint8Array {
@@ -317,24 +376,37 @@ function hmacSha256(key: Uint8Array, message: string): Uint8Array {
317
376
  return hmac(sha256, key, data);
318
377
  }
319
378
 
320
- function multiplierFromHash(hash: Uint8Array): number {
321
- if (hash.length < 4) {
322
- throw new Error("Hash must be at least 4 bytes.");
323
- }
379
+ function hmacSha256Bytes(key: Uint8Array, message: Uint8Array): Uint8Array {
380
+ return hmac(sha256, key, message);
381
+ }
324
382
 
325
- const value = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
326
- const normalized = value >>> 0;
327
- const max = 2 ** 32;
328
- const multiplierTimes100 = Math.floor((100 * max) / (max - normalized));
329
- return multiplierTimes100 / 100;
383
+ function u32FromHash(randomSource: RandomSource): number {
384
+ if (randomSource.length < 4) {
385
+ throw new Error("Random source must be at least 4 bytes.");
386
+ }
387
+ return (
388
+ ((randomSource[0] << 24) |
389
+ (randomSource[1] << 16) |
390
+ (randomSource[2] << 8) |
391
+ randomSource[3]) >>>
392
+ 0
393
+ );
330
394
  }
331
395
 
332
- function uniformFromHash(hash: Uint8Array): number {
333
- if (hash.length < 4) {
334
- throw new Error("Hash must be at least 4 bytes.");
396
+ function u32ToBytes(value: number): Uint8Array {
397
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
398
+ throw new Error("Value must be a 32-bit unsigned integer.");
335
399
  }
336
- const value = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
337
- const normalized = value >>> 0;
400
+ const buffer = new Uint8Array(4);
401
+ buffer[0] = (value >>> 24) & 0xff;
402
+ buffer[1] = (value >>> 16) & 0xff;
403
+ buffer[2] = (value >>> 8) & 0xff;
404
+ buffer[3] = value & 0xff;
405
+ return buffer;
406
+ }
407
+
408
+ function uniformFromHash(randomSource: RandomSource): number {
409
+ const normalized = u32FromHash(randomSource);
338
410
  return normalized / 2 ** 32;
339
411
  }
340
412