@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.
- package/dist/cli/context-codec.js +18 -10
- package/dist/compute-wager.d.ts +17 -13
- package/dist/compute-wager.js +83 -44
- package/dist/generated/context/index.d.ts +162 -157
- package/dist/generated/context/index.js +63 -48
- package/package.json +1 -1
- package/protobuf/context/index.proto +10 -8
- package/src/cli/context-codec.ts +18 -10
- package/src/compute-wager.ts +116 -58
- package/src/generated/context/index.ts +68 -51
|
@@ -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
|
|
8
|
-
const cleaned = input.replace(/\s+/g, "").replace(
|
|
9
|
-
|
|
10
|
-
|
|
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 -- <
|
|
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 -- <
|
|
49
|
+
"Usage: npm run decode -- <hex>",
|
|
42
50
|
" npm run encode -- '<json>'",
|
|
43
|
-
" cat context.
|
|
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
|
|
74
|
-
const bytes = Buffer.from(
|
|
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("
|
|
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)}`);
|
package/dist/compute-wager.d.ts
CHANGED
|
@@ -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
|
|
6
|
-
export
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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 {};
|
package/dist/compute-wager.js
CHANGED
|
@@ -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.
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
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(
|
|
23
|
-
const result = computeFairCoinTossResult(
|
|
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(
|
|
64
|
+
function doComputeCrashResult(randomSource, houseEdge) {
|
|
33
65
|
const nBits = 52;
|
|
34
|
-
const
|
|
35
|
-
const seed =
|
|
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(
|
|
50
|
-
const
|
|
51
|
-
|
|
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(
|
|
66
|
-
const seedHash = (0, sha256_1.sha256)(
|
|
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(
|
|
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)(
|
|
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(
|
|
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)(
|
|
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
|
|
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
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
259
|
+
return (((randomSource[0] << 24) |
|
|
260
|
+
(randomSource[1] << 16) |
|
|
261
|
+
(randomSource[2] << 8) |
|
|
262
|
+
randomSource[3]) >>>
|
|
263
|
+
0);
|
|
232
264
|
}
|
|
233
|
-
function
|
|
234
|
-
if (
|
|
235
|
-
throw new Error("
|
|
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
|
|
238
|
-
|
|
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) {
|