@actuallyfair/verifier 0.0.6 → 0.0.8

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.
@@ -4,10 +4,18 @@ const context_1 = require("../generated/context");
4
4
  function isMode(value) {
5
5
  return value === "decode" || value === "encode";
6
6
  }
7
- function normalizeBase64(input) {
8
- const cleaned = input.replace(/\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");
9
- const padding = cleaned.length % 4;
10
- return padding === 0 ? cleaned : `${cleaned}${"=".repeat(4 - padding)}`;
7
+ function normalizeHex(input) {
8
+ const cleaned = input.replace(/\s+/g, "").replace(/^0x/i, "");
9
+ if (!cleaned) {
10
+ return cleaned;
11
+ }
12
+ if (cleaned.length % 2 !== 0) {
13
+ throw new Error("Hex input must have an even length.");
14
+ }
15
+ if (!/^[0-9a-fA-F]+$/.test(cleaned)) {
16
+ throw new Error("Hex input contains non-hex characters.");
17
+ }
18
+ return cleaned;
11
19
  }
12
20
  function readStdin() {
13
21
  return new Promise((resolve, reject) => {
@@ -32,15 +40,15 @@ async function readInput(args) {
32
40
  }
33
41
  function usage(mode) {
34
42
  if (mode === "decode") {
35
- return ["Usage: npm run decode -- <base64>", " cat context.b64 | npm run decode"];
43
+ return ["Usage: npm run decode -- <hex>", " cat context.hex | npm run decode"];
36
44
  }
37
45
  if (mode === "encode") {
38
46
  return ["Usage: npm run encode -- '<json>'", " cat context.json | npm run encode"];
39
47
  }
40
48
  return [
41
- "Usage: npm run decode -- <base64>",
49
+ "Usage: npm run decode -- <hex>",
42
50
  " npm run encode -- '<json>'",
43
- " cat context.b64 | npm run decode",
51
+ " cat context.hex | npm run decode",
44
52
  " cat context.json | npm run encode",
45
53
  ];
46
54
  }
@@ -70,8 +78,8 @@ async function main() {
70
78
  }
71
79
  try {
72
80
  if (modeRaw === "decode") {
73
- const base64 = normalizeBase64(input);
74
- const bytes = Buffer.from(base64, "base64");
81
+ const hex = normalizeHex(input);
82
+ const bytes = Buffer.from(hex, "hex");
75
83
  const message = context_1.Context.decode(bytes);
76
84
  const json = context_1.Context.toJSON(message);
77
85
  process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
@@ -80,7 +88,7 @@ async function main() {
80
88
  const parsed = JSON.parse(input);
81
89
  const message = context_1.Context.fromJSON(parsed);
82
90
  const encoded = context_1.Context.encode(message).finish();
83
- process.stdout.write(`${Buffer.from(encoded).toString("base64")}\n`);
91
+ process.stdout.write(`${Buffer.from(encoded).toString("hex")}\n`);
84
92
  }
85
93
  catch (error) {
86
94
  console.error(`Failed to ${modeRaw} context: ${errorMessage(error)}`);
@@ -1,9 +1,18 @@
1
1
  import { Currency } from "./generated/currency";
2
2
  import { FairCoinToss, FairCoinToss_Choice } from "./generated/context/fair-coin-toss";
3
- import { CrashDice } from "./generated/context/crash-dice";
4
3
  import { MultiRoulette } from "./generated/context/multi-roulette";
5
- export declare function computeFairCoinTossResult(sig: Uint8Array): FairCoinToss_Choice.HEADS | FairCoinToss_Choice.TAILS;
6
- export declare function computeFairCoinTossOutcome(sig: Uint8Array, w: FairCoinToss): {
4
+ export type CompressedSeed = number;
5
+ export type RandomSource = Uint8Array;
6
+ export declare function splitHelixHash(hash: Uint8Array): {
7
+ lhs: Uint8Array;
8
+ rhs: Uint8Array;
9
+ };
10
+ export declare function computeCompressedSeed(clientSeed: string, lhsHash: Uint8Array): CompressedSeed;
11
+ export declare function computeCompressedSeedFromGameHash(clientSeed: string, gameHash: Uint8Array): CompressedSeed;
12
+ export declare function computeRandomSourceFromCompressedSeed(compressedSeed: CompressedSeed, rhsHash: Uint8Array): RandomSource;
13
+ export declare function computeRandomSource(clientSeed: string, gameHash: Uint8Array): RandomSource;
14
+ export declare function computeFairCoinTossResult(randomSource: RandomSource): FairCoinToss_Choice.HEADS | FairCoinToss_Choice.TAILS;
15
+ export declare function computeFairCoinTossOutcome(randomSource: RandomSource, w: FairCoinToss): {
7
16
  result: FairCoinToss_Choice;
8
17
  playerProfit: {
9
18
  currency: Currency;
@@ -12,15 +21,9 @@ export declare function computeFairCoinTossOutcome(sig: Uint8Array, w: FairCoinT
12
21
  };
13
22
  export declare function computeCrashResult(hash: Uint8Array, gameHash: Uint8Array, // This is the hash of the next from the hash chain
14
23
  houseEdge?: number): number;
15
- export type CrashDiceOutcome = {
16
- multiplier: number;
17
- target: number;
18
- win: boolean;
19
- };
20
- export declare function computeCrashDiceResult(hash: Uint8Array, clientSeed: string): number;
21
- export declare function computeCrashDiceOutcome(hash: Uint8Array, clientSeed: string, bet: CrashDice): CrashDiceOutcome;
22
- export declare function computeMultiRouletteResult(hash: Uint8Array, bet: MultiRoulette): number | undefined;
23
- export declare function computeMineLocations(hash: Uint8Array, revealedCells: Set<number>, // tiles we know are safe
24
+ export declare function computeCrashDiceResult(randomSource: RandomSource, houseEdge: number): number;
25
+ export declare function computeMultiRouletteResult(randomSource: RandomSource, bet: MultiRoulette): number | undefined;
26
+ export declare function computeMineLocations(randomSource: RandomSource, revealedCells: Set<number>, // tiles we know are safe
24
27
  cells: number, // how many cells in total
25
28
  mines: number): Set<number>;
26
29
  export declare function computeMinesMultiplier(mines: number, // how many mines in the game
@@ -29,12 +32,13 @@ turn: number, // which turn they have finished
29
32
  houseEdge?: number): number;
30
33
  type PlinkoPath = ("L" | "R")[];
31
34
  export declare function computePinkoPossibilityIndexFromPath(path: PlinkoPath): number;
32
- export declare function computePlinkoPath(hash: Uint8Array, possibilities: number): PlinkoPath;
35
+ export declare function computePlinkoPath(randomSource: RandomSource, possibilities: number): PlinkoPath;
33
36
  export declare function computePlinkoPascalsProbabilities(rowNumber: number): number[];
34
37
  export declare function computePlinkoHouseEdge(payouts: number[]): number;
35
38
  export type PlinkoResult = {
36
39
  slot: number;
37
40
  multiplier: number;
38
41
  };
42
+ export declare function computePlinkoResultFromRandomSource(randomSource: RandomSource, payouts: number[]): PlinkoResult;
39
43
  export declare function computePlinkoResult(hash: Uint8Array, clientSeed: string, payouts: number[]): PlinkoResult;
40
44
  export {};
@@ -1,15 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.computePlinkoResult = exports.computePlinkoHouseEdge = exports.computePlinkoPascalsProbabilities = exports.computePlinkoPath = exports.computePinkoPossibilityIndexFromPath = exports.computeMinesMultiplier = exports.computeMineLocations = exports.computeMultiRouletteResult = exports.computeCrashDiceOutcome = exports.computeCrashDiceResult = exports.computeCrashResult = exports.computeFairCoinTossOutcome = exports.computeFairCoinTossResult = void 0;
3
+ exports.computePlinkoResult = exports.computePlinkoResultFromRandomSource = exports.computePlinkoHouseEdge = exports.computePlinkoPascalsProbabilities = exports.computePlinkoPath = exports.computePinkoPossibilityIndexFromPath = exports.computeMinesMultiplier = exports.computeMineLocations = exports.computeMultiRouletteResult = exports.computeCrashDiceResult = exports.computeCrashResult = exports.computeFairCoinTossOutcome = exports.computeFairCoinTossResult = exports.computeRandomSource = exports.computeRandomSourceFromCompressedSeed = exports.computeCompressedSeedFromGameHash = exports.computeCompressedSeed = exports.splitHelixHash = void 0;
4
4
  const sha256_1 = require("@noble/hashes/sha256");
5
5
  const hmac_1 = require("@noble/hashes/hmac");
6
6
  const utils_1 = require("@noble/curves/abstract/utils");
7
7
  const currency_1 = require("./generated/currency");
8
8
  const fair_coin_toss_1 = require("./generated/context/fair-coin-toss");
9
9
  const utils_2 = require("@noble/hashes/utils");
10
- function computeFairCoinTossResult(sig) {
11
- // We're going to hash the signature just to really be sure its fairly distributed
12
- const hash = (0, sha256_1.sha256)(sig);
10
+ function splitHelixHash(hash) {
11
+ if (hash.length % 2 !== 0) {
12
+ throw new Error("Helix hash input must have an even number of bytes.");
13
+ }
14
+ const mid = hash.length >>> 1;
15
+ return {
16
+ lhs: hash.subarray(0, mid),
17
+ rhs: hash.subarray(mid),
18
+ };
19
+ }
20
+ exports.splitHelixHash = splitHelixHash;
21
+ function computeCompressedSeed(clientSeed, lhsHash) {
22
+ const seedHash = hmacSha256(lhsHash, clientSeed);
23
+ return u32FromHash(seedHash);
24
+ }
25
+ exports.computeCompressedSeed = computeCompressedSeed;
26
+ function computeCompressedSeedFromGameHash(clientSeed, gameHash) {
27
+ const { lhs } = splitHelixHash(gameHash);
28
+ return computeCompressedSeed(clientSeed, lhs);
29
+ }
30
+ exports.computeCompressedSeedFromGameHash = computeCompressedSeedFromGameHash;
31
+ function computeRandomSourceFromCompressedSeed(compressedSeed, rhsHash) {
32
+ const seedBytes = u32ToBytes(compressedSeed);
33
+ return hmacSha256Bytes(rhsHash, seedBytes);
34
+ }
35
+ exports.computeRandomSourceFromCompressedSeed = computeRandomSourceFromCompressedSeed;
36
+ function computeRandomSource(clientSeed, gameHash) {
37
+ const { lhs, rhs } = splitHelixHash(gameHash);
38
+ const compressedSeed = computeCompressedSeed(clientSeed, lhs);
39
+ return computeRandomSourceFromCompressedSeed(compressedSeed, rhs);
40
+ }
41
+ exports.computeRandomSource = computeRandomSource;
42
+ function computeFairCoinTossResult(randomSource) {
43
+ // We're going to hash the random source just to really be sure its fairly distributed
44
+ const hash = (0, sha256_1.sha256)(randomSource);
13
45
  const result = hash[0] % 2;
14
46
  if (result == 0) {
15
47
  return fair_coin_toss_1.FairCoinToss_Choice.HEADS;
@@ -19,8 +51,8 @@ function computeFairCoinTossResult(sig) {
19
51
  }
20
52
  }
21
53
  exports.computeFairCoinTossResult = computeFairCoinTossResult;
22
- function computeFairCoinTossOutcome(sig, w) {
23
- const result = computeFairCoinTossResult(sig);
54
+ function computeFairCoinTossOutcome(randomSource, w) {
55
+ const result = computeFairCoinTossResult(randomSource);
24
56
  const win = w.playerChoice === result;
25
57
  const profit = win ? 1 : -1;
26
58
  return {
@@ -29,10 +61,10 @@ function computeFairCoinTossOutcome(sig, w) {
29
61
  };
30
62
  }
31
63
  exports.computeFairCoinTossOutcome = computeFairCoinTossOutcome;
32
- function doComputeCrashResult(hash, houseEdge) {
64
+ function doComputeCrashResult(randomSource, houseEdge) {
33
65
  const nBits = 52;
34
- const hashHex = (0, utils_2.bytesToHex)(hash);
35
- const seed = hashHex.slice(0, nBits / 4);
66
+ const randomSourceHex = (0, utils_2.bytesToHex)(randomSource);
67
+ const seed = randomSourceHex.slice(0, nBits / 4);
36
68
  const r = Number.parseInt(seed, 16);
37
69
  let X = r / 2 ** nBits; // uniformly distributed in [0; 1)
38
70
  let Y = 1 - X; // Now it's uniformly distributed in (0; 1], so it's safe to divide by it
@@ -46,24 +78,17 @@ houseEdge = 0) {
46
78
  return doComputeCrashResult((0, hmac_1.hmac)(sha256_1.sha256, hash, gameHash), houseEdge);
47
79
  }
48
80
  exports.computeCrashResult = computeCrashResult;
49
- function computeCrashDiceResult(hash, clientSeed) {
50
- const rollHash = hmacSha256(hash, clientSeed);
51
- return multiplierFromHash(rollHash);
81
+ function computeCrashDiceResult(randomSource, houseEdge) {
82
+ const normalized = u32FromHash(randomSource);
83
+ const max = 2 ** 32;
84
+ let result = ((1 - houseEdge) * max) / (max - normalized);
85
+ result = Math.floor(result * 100) / 100;
86
+ return Math.max(1, result);
52
87
  }
53
88
  exports.computeCrashDiceResult = computeCrashDiceResult;
54
- function computeCrashDiceOutcome(hash, clientSeed, bet) {
55
- const multiplier = computeCrashDiceResult(hash, clientSeed);
56
- const target = bet.target;
57
- return {
58
- multiplier,
59
- target,
60
- win: multiplier >= target,
61
- };
62
- }
63
- exports.computeCrashDiceOutcome = computeCrashDiceOutcome;
64
89
  // returns the index of which roulette outcome was picked
65
- function computeMultiRouletteResult(hash, bet) {
66
- const seedHash = (0, sha256_1.sha256)(hash);
90
+ function computeMultiRouletteResult(randomSource, bet) {
91
+ const seedHash = (0, sha256_1.sha256)(randomSource);
67
92
  const nBits = 52;
68
93
  const hashHex = (0, utils_2.bytesToHex)(seedHash);
69
94
  const seed = hashHex.slice(0, nBits / 4);
@@ -78,7 +103,7 @@ function computeMultiRouletteResult(hash, bet) {
78
103
  }
79
104
  }
80
105
  exports.computeMultiRouletteResult = computeMultiRouletteResult;
81
- function computeMineLocations(hash, revealedCells, // tiles we know are safe
106
+ function computeMineLocations(randomSource, revealedCells, // tiles we know are safe
82
107
  cells, // how many cells in total
83
108
  mines // how many mines there are going to be in total
84
109
  ) {
@@ -89,7 +114,7 @@ mines // how many mines there are going to be in total
89
114
  console.warn("Trying to place more mines than there are available locations.");
90
115
  break;
91
116
  }
92
- let mineIndex = Number((0, utils_1.bytesToNumberBE)(hash) % BigInt(cellsLeft));
117
+ let mineIndex = Number((0, utils_1.bytesToNumberBE)(randomSource) % BigInt(cellsLeft));
93
118
  let adjustedIndex = 0;
94
119
  for (let i = 0; i < cells; i++) {
95
120
  if (revealedCells.has(i) || mineLocations.has(i))
@@ -151,13 +176,13 @@ function computePinkoPossibilityIndexFromPath(path) {
151
176
  exports.computePinkoPossibilityIndexFromPath = computePinkoPossibilityIndexFromPath;
152
177
  // return a path (saying 'L' or 'R', where 'L' means go left, and 'R' means going right)
153
178
  // of possibilities-1 length
154
- function computePlinkoPath(hash, possibilities) {
179
+ function computePlinkoPath(randomSource, possibilities) {
155
180
  if (!Number.isSafeInteger(possibilities) ||
156
181
  possibilities < 2 ||
157
182
  possibilities > 256) {
158
183
  throw new Error("invalid possibilities ");
159
184
  }
160
- const pathHash = (0, sha256_1.sha256)(hash);
185
+ const pathHash = (0, sha256_1.sha256)(randomSource);
161
186
  let n = (0, utils_1.bytesToNumberBE)(pathHash);
162
187
  let ret = [];
163
188
  for (let i = 0; i < possibilities - 1; i++) {
@@ -201,13 +226,12 @@ function computePlinkoHouseEdge(payouts) {
201
226
  return ev;
202
227
  }
203
228
  exports.computePlinkoHouseEdge = computePlinkoHouseEdge;
204
- function computePlinkoResult(hash, clientSeed, payouts) {
229
+ function computePlinkoResultFromRandomSource(randomSource, payouts) {
205
230
  if (payouts.length < 2) {
206
231
  throw new Error("invalid possibilities ");
207
232
  }
208
233
  const probabilities = computePlinkoPascalsProbabilities(payouts.length);
209
- const rollHash = hmacSha256(hash, clientSeed);
210
- const roll = uniformFromHash(rollHash);
234
+ const roll = uniformFromHash(randomSource);
211
235
  const slot = pickSlot(probabilities, roll);
212
236
  const multiplier = payouts[slot] ?? 0;
213
237
  return {
@@ -215,27 +239,42 @@ function computePlinkoResult(hash, clientSeed, payouts) {
215
239
  multiplier,
216
240
  };
217
241
  }
242
+ exports.computePlinkoResultFromRandomSource = computePlinkoResultFromRandomSource;
243
+ function computePlinkoResult(hash, clientSeed, payouts) {
244
+ const randomSource = computeRandomSource(clientSeed, hash);
245
+ return computePlinkoResultFromRandomSource(randomSource, payouts);
246
+ }
218
247
  exports.computePlinkoResult = computePlinkoResult;
219
248
  function hmacSha256(key, message) {
220
249
  const data = new TextEncoder().encode(message);
221
250
  return (0, hmac_1.hmac)(sha256_1.sha256, key, data);
222
251
  }
223
- function multiplierFromHash(hash) {
224
- if (hash.length < 4) {
225
- throw new Error("Hash must be at least 4 bytes.");
252
+ function hmacSha256Bytes(key, message) {
253
+ return (0, hmac_1.hmac)(sha256_1.sha256, key, message);
254
+ }
255
+ function u32FromHash(randomSource) {
256
+ if (randomSource.length < 4) {
257
+ throw new Error("Random source must be at least 4 bytes.");
226
258
  }
227
- const value = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
228
- const normalized = value >>> 0;
229
- const max = 2 ** 32;
230
- const multiplierTimes100 = Math.floor((100 * max) / (max - normalized));
231
- return multiplierTimes100 / 100;
259
+ return (((randomSource[0] << 24) |
260
+ (randomSource[1] << 16) |
261
+ (randomSource[2] << 8) |
262
+ randomSource[3]) >>>
263
+ 0);
232
264
  }
233
- function uniformFromHash(hash) {
234
- if (hash.length < 4) {
235
- throw new Error("Hash must be at least 4 bytes.");
265
+ function u32ToBytes(value) {
266
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
267
+ throw new Error("Value must be a 32-bit unsigned integer.");
236
268
  }
237
- const value = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
238
- const normalized = value >>> 0;
269
+ const buffer = new Uint8Array(4);
270
+ buffer[0] = (value >>> 24) & 0xff;
271
+ buffer[1] = (value >>> 16) & 0xff;
272
+ buffer[2] = (value >>> 8) & 0xff;
273
+ buffer[3] = value & 0xff;
274
+ return buffer;
275
+ }
276
+ function uniformFromHash(randomSource) {
277
+ const normalized = u32FromHash(randomSource);
239
278
  return normalized / 2 ** 32;
240
279
  }
241
280
  function pickSlot(probabilities, roll) {