@actuallyfair/verifier 0.0.6 → 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.
- package/dist/cli/context-codec.js +18 -10
- package/dist/compute-wager.d.ts +18 -5
- package/dist/compute-wager.js +93 -35
- 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 +130 -39
- package/src/generated/context/index.ts +68 -51
|
@@ -16,45 +16,49 @@ const plinko_1 = require("./plinko");
|
|
|
16
16
|
const tower_1 = require("./tower");
|
|
17
17
|
function createBaseContext() {
|
|
18
18
|
return {
|
|
19
|
+
compressedSeed: 0,
|
|
20
|
+
init: undefined,
|
|
21
|
+
plinko: undefined,
|
|
22
|
+
crashDice: undefined,
|
|
19
23
|
fairCoinToss: undefined,
|
|
20
24
|
crash: undefined,
|
|
21
25
|
hilo: undefined,
|
|
22
|
-
crashDice: undefined,
|
|
23
26
|
multiRoulette: undefined,
|
|
24
27
|
mines: undefined,
|
|
25
28
|
tower: undefined,
|
|
26
|
-
plinko: undefined,
|
|
27
|
-
init: undefined,
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
exports.Context = {
|
|
31
32
|
encode(message, writer = minimal_1.default.Writer.create()) {
|
|
32
|
-
if (message.
|
|
33
|
-
|
|
33
|
+
if (message.compressedSeed !== 0) {
|
|
34
|
+
writer.uint32(13).fixed32(message.compressedSeed);
|
|
34
35
|
}
|
|
35
|
-
if (message.
|
|
36
|
-
|
|
36
|
+
if (message.init !== undefined) {
|
|
37
|
+
exports.PosthashInit.encode(message.init, writer.uint32(18).fork()).ldelim();
|
|
37
38
|
}
|
|
38
|
-
if (message.
|
|
39
|
-
|
|
39
|
+
if (message.plinko !== undefined) {
|
|
40
|
+
plinko_1.Plinko.encode(message.plinko, writer.uint32(26).fork()).ldelim();
|
|
40
41
|
}
|
|
41
42
|
if (message.crashDice !== undefined) {
|
|
42
43
|
crash_dice_1.CrashDice.encode(message.crashDice, writer.uint32(34).fork()).ldelim();
|
|
43
44
|
}
|
|
45
|
+
if (message.fairCoinToss !== undefined) {
|
|
46
|
+
fair_coin_toss_1.FairCoinToss.encode(message.fairCoinToss, writer.uint32(42).fork()).ldelim();
|
|
47
|
+
}
|
|
48
|
+
if (message.crash !== undefined) {
|
|
49
|
+
crash_1.Crash.encode(message.crash, writer.uint32(50).fork()).ldelim();
|
|
50
|
+
}
|
|
51
|
+
if (message.hilo !== undefined) {
|
|
52
|
+
hilo_1.HiLo.encode(message.hilo, writer.uint32(58).fork()).ldelim();
|
|
53
|
+
}
|
|
44
54
|
if (message.multiRoulette !== undefined) {
|
|
45
|
-
multi_roulette_1.MultiRoulette.encode(message.multiRoulette, writer.uint32(
|
|
55
|
+
multi_roulette_1.MultiRoulette.encode(message.multiRoulette, writer.uint32(66).fork()).ldelim();
|
|
46
56
|
}
|
|
47
57
|
if (message.mines !== undefined) {
|
|
48
|
-
mines_1.Mines.encode(message.mines, writer.uint32(
|
|
58
|
+
mines_1.Mines.encode(message.mines, writer.uint32(74).fork()).ldelim();
|
|
49
59
|
}
|
|
50
60
|
if (message.tower !== undefined) {
|
|
51
|
-
tower_1.Tower.encode(message.tower, writer.uint32(
|
|
52
|
-
}
|
|
53
|
-
if (message.plinko !== undefined) {
|
|
54
|
-
plinko_1.Plinko.encode(message.plinko, writer.uint32(66).fork()).ldelim();
|
|
55
|
-
}
|
|
56
|
-
if (message.init !== undefined) {
|
|
57
|
-
exports.PosthashInit.encode(message.init, writer.uint32(74).fork()).ldelim();
|
|
61
|
+
tower_1.Tower.encode(message.tower, writer.uint32(82).fork()).ldelim();
|
|
58
62
|
}
|
|
59
63
|
return writer;
|
|
60
64
|
},
|
|
@@ -66,22 +70,22 @@ exports.Context = {
|
|
|
66
70
|
const tag = reader.uint32();
|
|
67
71
|
switch (tag >>> 3) {
|
|
68
72
|
case 1:
|
|
69
|
-
if (tag !==
|
|
73
|
+
if (tag !== 13) {
|
|
70
74
|
break;
|
|
71
75
|
}
|
|
72
|
-
message.
|
|
76
|
+
message.compressedSeed = reader.fixed32();
|
|
73
77
|
continue;
|
|
74
78
|
case 2:
|
|
75
79
|
if (tag !== 18) {
|
|
76
80
|
break;
|
|
77
81
|
}
|
|
78
|
-
message.
|
|
82
|
+
message.init = exports.PosthashInit.decode(reader, reader.uint32());
|
|
79
83
|
continue;
|
|
80
84
|
case 3:
|
|
81
85
|
if (tag !== 26) {
|
|
82
86
|
break;
|
|
83
87
|
}
|
|
84
|
-
message.
|
|
88
|
+
message.plinko = plinko_1.Plinko.decode(reader, reader.uint32());
|
|
85
89
|
continue;
|
|
86
90
|
case 4:
|
|
87
91
|
if (tag !== 34) {
|
|
@@ -93,31 +97,37 @@ exports.Context = {
|
|
|
93
97
|
if (tag !== 42) {
|
|
94
98
|
break;
|
|
95
99
|
}
|
|
96
|
-
message.
|
|
100
|
+
message.fairCoinToss = fair_coin_toss_1.FairCoinToss.decode(reader, reader.uint32());
|
|
97
101
|
continue;
|
|
98
102
|
case 6:
|
|
99
103
|
if (tag !== 50) {
|
|
100
104
|
break;
|
|
101
105
|
}
|
|
102
|
-
message.
|
|
106
|
+
message.crash = crash_1.Crash.decode(reader, reader.uint32());
|
|
103
107
|
continue;
|
|
104
108
|
case 7:
|
|
105
109
|
if (tag !== 58) {
|
|
106
110
|
break;
|
|
107
111
|
}
|
|
108
|
-
message.
|
|
112
|
+
message.hilo = hilo_1.HiLo.decode(reader, reader.uint32());
|
|
109
113
|
continue;
|
|
110
114
|
case 8:
|
|
111
115
|
if (tag !== 66) {
|
|
112
116
|
break;
|
|
113
117
|
}
|
|
114
|
-
message.
|
|
118
|
+
message.multiRoulette = multi_roulette_1.MultiRoulette.decode(reader, reader.uint32());
|
|
115
119
|
continue;
|
|
116
120
|
case 9:
|
|
117
121
|
if (tag !== 74) {
|
|
118
122
|
break;
|
|
119
123
|
}
|
|
120
|
-
message.
|
|
124
|
+
message.mines = mines_1.Mines.decode(reader, reader.uint32());
|
|
125
|
+
continue;
|
|
126
|
+
case 10:
|
|
127
|
+
if (tag !== 82) {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
message.tower = tower_1.Tower.decode(reader, reader.uint32());
|
|
121
131
|
continue;
|
|
122
132
|
}
|
|
123
133
|
if ((tag & 7) === 4 || tag === 0) {
|
|
@@ -129,19 +139,32 @@ exports.Context = {
|
|
|
129
139
|
},
|
|
130
140
|
fromJSON(object) {
|
|
131
141
|
return {
|
|
142
|
+
compressedSeed: isSet(object.compressedSeed) ? globalThis.Number(object.compressedSeed) : 0,
|
|
143
|
+
init: isSet(object.init) ? exports.PosthashInit.fromJSON(object.init) : undefined,
|
|
144
|
+
plinko: isSet(object.plinko) ? plinko_1.Plinko.fromJSON(object.plinko) : undefined,
|
|
145
|
+
crashDice: isSet(object.crashDice) ? crash_dice_1.CrashDice.fromJSON(object.crashDice) : undefined,
|
|
132
146
|
fairCoinToss: isSet(object.fairCoinToss) ? fair_coin_toss_1.FairCoinToss.fromJSON(object.fairCoinToss) : undefined,
|
|
133
147
|
crash: isSet(object.crash) ? crash_1.Crash.fromJSON(object.crash) : undefined,
|
|
134
148
|
hilo: isSet(object.hilo) ? hilo_1.HiLo.fromJSON(object.hilo) : undefined,
|
|
135
|
-
crashDice: isSet(object.crashDice) ? crash_dice_1.CrashDice.fromJSON(object.crashDice) : undefined,
|
|
136
149
|
multiRoulette: isSet(object.multiRoulette) ? multi_roulette_1.MultiRoulette.fromJSON(object.multiRoulette) : undefined,
|
|
137
150
|
mines: isSet(object.mines) ? mines_1.Mines.fromJSON(object.mines) : undefined,
|
|
138
151
|
tower: isSet(object.tower) ? tower_1.Tower.fromJSON(object.tower) : undefined,
|
|
139
|
-
plinko: isSet(object.plinko) ? plinko_1.Plinko.fromJSON(object.plinko) : undefined,
|
|
140
|
-
init: isSet(object.init) ? exports.PosthashInit.fromJSON(object.init) : undefined,
|
|
141
152
|
};
|
|
142
153
|
},
|
|
143
154
|
toJSON(message) {
|
|
144
155
|
const obj = {};
|
|
156
|
+
if (message.compressedSeed !== 0) {
|
|
157
|
+
obj.compressedSeed = Math.round(message.compressedSeed);
|
|
158
|
+
}
|
|
159
|
+
if (message.init !== undefined) {
|
|
160
|
+
obj.init = exports.PosthashInit.toJSON(message.init);
|
|
161
|
+
}
|
|
162
|
+
if (message.plinko !== undefined) {
|
|
163
|
+
obj.plinko = plinko_1.Plinko.toJSON(message.plinko);
|
|
164
|
+
}
|
|
165
|
+
if (message.crashDice !== undefined) {
|
|
166
|
+
obj.crashDice = crash_dice_1.CrashDice.toJSON(message.crashDice);
|
|
167
|
+
}
|
|
145
168
|
if (message.fairCoinToss !== undefined) {
|
|
146
169
|
obj.fairCoinToss = fair_coin_toss_1.FairCoinToss.toJSON(message.fairCoinToss);
|
|
147
170
|
}
|
|
@@ -151,9 +174,6 @@ exports.Context = {
|
|
|
151
174
|
if (message.hilo !== undefined) {
|
|
152
175
|
obj.hilo = hilo_1.HiLo.toJSON(message.hilo);
|
|
153
176
|
}
|
|
154
|
-
if (message.crashDice !== undefined) {
|
|
155
|
-
obj.crashDice = crash_dice_1.CrashDice.toJSON(message.crashDice);
|
|
156
|
-
}
|
|
157
177
|
if (message.multiRoulette !== undefined) {
|
|
158
178
|
obj.multiRoulette = multi_roulette_1.MultiRoulette.toJSON(message.multiRoulette);
|
|
159
179
|
}
|
|
@@ -163,12 +183,6 @@ exports.Context = {
|
|
|
163
183
|
if (message.tower !== undefined) {
|
|
164
184
|
obj.tower = tower_1.Tower.toJSON(message.tower);
|
|
165
185
|
}
|
|
166
|
-
if (message.plinko !== undefined) {
|
|
167
|
-
obj.plinko = plinko_1.Plinko.toJSON(message.plinko);
|
|
168
|
-
}
|
|
169
|
-
if (message.init !== undefined) {
|
|
170
|
-
obj.init = exports.PosthashInit.toJSON(message.init);
|
|
171
|
-
}
|
|
172
186
|
return obj;
|
|
173
187
|
},
|
|
174
188
|
create(base) {
|
|
@@ -176,25 +190,26 @@ exports.Context = {
|
|
|
176
190
|
},
|
|
177
191
|
fromPartial(object) {
|
|
178
192
|
const message = createBaseContext();
|
|
193
|
+
message.compressedSeed = object.compressedSeed ?? 0;
|
|
194
|
+
message.init = (object.init !== undefined && object.init !== null)
|
|
195
|
+
? exports.PosthashInit.fromPartial(object.init)
|
|
196
|
+
: undefined;
|
|
197
|
+
message.plinko = (object.plinko !== undefined && object.plinko !== null)
|
|
198
|
+
? plinko_1.Plinko.fromPartial(object.plinko)
|
|
199
|
+
: undefined;
|
|
200
|
+
message.crashDice = (object.crashDice !== undefined && object.crashDice !== null)
|
|
201
|
+
? crash_dice_1.CrashDice.fromPartial(object.crashDice)
|
|
202
|
+
: undefined;
|
|
179
203
|
message.fairCoinToss = (object.fairCoinToss !== undefined && object.fairCoinToss !== null)
|
|
180
204
|
? fair_coin_toss_1.FairCoinToss.fromPartial(object.fairCoinToss)
|
|
181
205
|
: undefined;
|
|
182
206
|
message.crash = (object.crash !== undefined && object.crash !== null) ? crash_1.Crash.fromPartial(object.crash) : undefined;
|
|
183
207
|
message.hilo = (object.hilo !== undefined && object.hilo !== null) ? hilo_1.HiLo.fromPartial(object.hilo) : undefined;
|
|
184
|
-
message.crashDice = (object.crashDice !== undefined && object.crashDice !== null)
|
|
185
|
-
? crash_dice_1.CrashDice.fromPartial(object.crashDice)
|
|
186
|
-
: undefined;
|
|
187
208
|
message.multiRoulette = (object.multiRoulette !== undefined && object.multiRoulette !== null)
|
|
188
209
|
? multi_roulette_1.MultiRoulette.fromPartial(object.multiRoulette)
|
|
189
210
|
: undefined;
|
|
190
211
|
message.mines = (object.mines !== undefined && object.mines !== null) ? mines_1.Mines.fromPartial(object.mines) : undefined;
|
|
191
212
|
message.tower = (object.tower !== undefined && object.tower !== null) ? tower_1.Tower.fromPartial(object.tower) : undefined;
|
|
192
|
-
message.plinko = (object.plinko !== undefined && object.plinko !== null)
|
|
193
|
-
? plinko_1.Plinko.fromPartial(object.plinko)
|
|
194
|
-
: undefined;
|
|
195
|
-
message.init = (object.init !== undefined && object.init !== null)
|
|
196
|
-
? exports.PosthashInit.fromPartial(object.init)
|
|
197
|
-
: undefined;
|
|
198
213
|
return message;
|
|
199
214
|
},
|
|
200
215
|
};
|
package/package.json
CHANGED
|
@@ -10,16 +10,18 @@ import "context/tower.proto";
|
|
|
10
10
|
import "context/plinko.proto";
|
|
11
11
|
|
|
12
12
|
message Context {
|
|
13
|
+
fixed32 compressed_seed = 1;
|
|
13
14
|
oneof context_type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
HiLo hilo = 3;
|
|
15
|
+
PosthashInit init = 2;
|
|
16
|
+
Plinko plinko = 3;
|
|
17
17
|
CrashDice crash_dice = 4;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
FairCoinToss fair_coin_toss = 5;
|
|
20
|
+
Crash crash = 6;
|
|
21
|
+
HiLo hilo = 7;
|
|
22
|
+
MultiRoulette multi_roulette = 8;
|
|
23
|
+
Mines mines = 9;
|
|
24
|
+
Tower tower = 10;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
|
package/src/cli/context-codec.ts
CHANGED
|
@@ -6,10 +6,18 @@ function isMode(value: string | undefined): value is Mode {
|
|
|
6
6
|
return value === "decode" || value === "encode";
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function
|
|
10
|
-
const cleaned = input.replace(/\s+/g, "").replace(
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
function normalizeHex(input: string): string {
|
|
10
|
+
const cleaned = input.replace(/\s+/g, "").replace(/^0x/i, "");
|
|
11
|
+
if (!cleaned) {
|
|
12
|
+
return cleaned;
|
|
13
|
+
}
|
|
14
|
+
if (cleaned.length % 2 !== 0) {
|
|
15
|
+
throw new Error("Hex input must have an even length.");
|
|
16
|
+
}
|
|
17
|
+
if (!/^[0-9a-fA-F]+$/.test(cleaned)) {
|
|
18
|
+
throw new Error("Hex input contains non-hex characters.");
|
|
19
|
+
}
|
|
20
|
+
return cleaned;
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
function readStdin(): Promise<string> {
|
|
@@ -39,15 +47,15 @@ async function readInput(args: string[]): Promise<string> {
|
|
|
39
47
|
|
|
40
48
|
function usage(mode?: Mode): string[] {
|
|
41
49
|
if (mode === "decode") {
|
|
42
|
-
return ["Usage: npm run decode -- <
|
|
50
|
+
return ["Usage: npm run decode -- <hex>", " cat context.hex | npm run decode"];
|
|
43
51
|
}
|
|
44
52
|
if (mode === "encode") {
|
|
45
53
|
return ["Usage: npm run encode -- '<json>'", " cat context.json | npm run encode"];
|
|
46
54
|
}
|
|
47
55
|
return [
|
|
48
|
-
"Usage: npm run decode -- <
|
|
56
|
+
"Usage: npm run decode -- <hex>",
|
|
49
57
|
" npm run encode -- '<json>'",
|
|
50
|
-
" cat context.
|
|
58
|
+
" cat context.hex | npm run decode",
|
|
51
59
|
" cat context.json | npm run encode",
|
|
52
60
|
];
|
|
53
61
|
}
|
|
@@ -82,8 +90,8 @@ async function main(): Promise<void> {
|
|
|
82
90
|
|
|
83
91
|
try {
|
|
84
92
|
if (modeRaw === "decode") {
|
|
85
|
-
const
|
|
86
|
-
const bytes = Buffer.from(
|
|
93
|
+
const hex = normalizeHex(input);
|
|
94
|
+
const bytes = Buffer.from(hex, "hex");
|
|
87
95
|
const message = Context.decode(bytes);
|
|
88
96
|
const json = Context.toJSON(message);
|
|
89
97
|
process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
|
|
@@ -93,7 +101,7 @@ async function main(): Promise<void> {
|
|
|
93
101
|
const parsed = JSON.parse(input) as unknown;
|
|
94
102
|
const message = Context.fromJSON(parsed);
|
|
95
103
|
const encoded = Context.encode(message).finish();
|
|
96
|
-
process.stdout.write(`${Buffer.from(encoded).toString("
|
|
104
|
+
process.stdout.write(`${Buffer.from(encoded).toString("hex")}\n`);
|
|
97
105
|
} catch (error) {
|
|
98
106
|
console.error(`Failed to ${modeRaw} context: ${errorMessage(error)}`);
|
|
99
107
|
process.exit(1);
|
package/src/compute-wager.ts
CHANGED
|
@@ -15,9 +15,60 @@ 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
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
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);
|
|
21
72
|
const result = hash[0] % 2;
|
|
22
73
|
if (result == 0) {
|
|
23
74
|
return FairCoinToss_Choice.HEADS;
|
|
@@ -26,8 +77,11 @@ export function computeFairCoinTossResult(sig: Uint8Array) {
|
|
|
26
77
|
}
|
|
27
78
|
}
|
|
28
79
|
|
|
29
|
-
export function computeFairCoinTossOutcome(
|
|
30
|
-
|
|
80
|
+
export function computeFairCoinTossOutcome(
|
|
81
|
+
randomSource: RandomSource,
|
|
82
|
+
w: FairCoinToss
|
|
83
|
+
) {
|
|
84
|
+
const result = computeFairCoinTossResult(randomSource);
|
|
31
85
|
|
|
32
86
|
const win = w.playerChoice === result;
|
|
33
87
|
|
|
@@ -39,11 +93,11 @@ export function computeFairCoinTossOutcome(sig: Uint8Array, w: FairCoinToss) {
|
|
|
39
93
|
};
|
|
40
94
|
}
|
|
41
95
|
|
|
42
|
-
function doComputeCrashResult(
|
|
96
|
+
function doComputeCrashResult(randomSource: RandomSource, houseEdge: number) {
|
|
43
97
|
const nBits = 52;
|
|
44
|
-
const
|
|
98
|
+
const randomSourceHex = bytesToHex(randomSource);
|
|
45
99
|
|
|
46
|
-
const seed =
|
|
100
|
+
const seed = randomSourceHex.slice(0, nBits / 4);
|
|
47
101
|
const r = Number.parseInt(seed, 16);
|
|
48
102
|
|
|
49
103
|
let X = r / 2 ** nBits; // uniformly distributed in [0; 1)
|
|
@@ -72,17 +126,25 @@ export type CrashDiceOutcome = {
|
|
|
72
126
|
win: boolean;
|
|
73
127
|
};
|
|
74
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
|
+
|
|
75
138
|
export function computeCrashDiceResult(hash: Uint8Array, clientSeed: string) {
|
|
76
|
-
const
|
|
77
|
-
return
|
|
139
|
+
const randomSource = computeRandomSource(clientSeed, hash);
|
|
140
|
+
return computeCrashDiceResultFromRandomSource(randomSource);
|
|
78
141
|
}
|
|
79
142
|
|
|
80
|
-
export function
|
|
81
|
-
|
|
82
|
-
clientSeed: string,
|
|
143
|
+
export function computeCrashDiceOutcomeFromRandomSource(
|
|
144
|
+
randomSource: RandomSource,
|
|
83
145
|
bet: CrashDice
|
|
84
146
|
): CrashDiceOutcome {
|
|
85
|
-
const multiplier =
|
|
147
|
+
const multiplier = computeCrashDiceResultFromRandomSource(randomSource);
|
|
86
148
|
const target = bet.target;
|
|
87
149
|
|
|
88
150
|
return {
|
|
@@ -92,12 +154,21 @@ export function computeCrashDiceOutcome(
|
|
|
92
154
|
};
|
|
93
155
|
}
|
|
94
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
|
+
|
|
95
166
|
// returns the index of which roulette outcome was picked
|
|
96
167
|
export function computeMultiRouletteResult(
|
|
97
|
-
|
|
168
|
+
randomSource: RandomSource,
|
|
98
169
|
bet: MultiRoulette
|
|
99
170
|
) {
|
|
100
|
-
const seedHash = sha256(
|
|
171
|
+
const seedHash = sha256(randomSource);
|
|
101
172
|
|
|
102
173
|
const nBits = 52;
|
|
103
174
|
const hashHex = bytesToHex(seedHash);
|
|
@@ -117,7 +188,7 @@ export function computeMultiRouletteResult(
|
|
|
117
188
|
}
|
|
118
189
|
|
|
119
190
|
export function computeMineLocations(
|
|
120
|
-
|
|
191
|
+
randomSource: RandomSource,
|
|
121
192
|
revealedCells: Set<number>, // tiles we know are safe
|
|
122
193
|
cells: number, // how many cells in total
|
|
123
194
|
mines: number // how many mines there are going to be in total
|
|
@@ -134,7 +205,7 @@ export function computeMineLocations(
|
|
|
134
205
|
break;
|
|
135
206
|
}
|
|
136
207
|
|
|
137
|
-
let mineIndex = Number(bytesToNumberBE(
|
|
208
|
+
let mineIndex = Number(bytesToNumberBE(randomSource) % BigInt(cellsLeft));
|
|
138
209
|
let adjustedIndex = 0;
|
|
139
210
|
|
|
140
211
|
for (let i = 0; i < cells; i++) {
|
|
@@ -209,7 +280,7 @@ export function computePinkoPossibilityIndexFromPath(path: PlinkoPath) {
|
|
|
209
280
|
// return a path (saying 'L' or 'R', where 'L' means go left, and 'R' means going right)
|
|
210
281
|
// of possibilities-1 length
|
|
211
282
|
export function computePlinkoPath(
|
|
212
|
-
|
|
283
|
+
randomSource: RandomSource,
|
|
213
284
|
possibilities: number
|
|
214
285
|
): PlinkoPath {
|
|
215
286
|
if (
|
|
@@ -219,7 +290,7 @@ export function computePlinkoPath(
|
|
|
219
290
|
) {
|
|
220
291
|
throw new Error("invalid possibilities ");
|
|
221
292
|
}
|
|
222
|
-
const pathHash = sha256(
|
|
293
|
+
const pathHash = sha256(randomSource);
|
|
223
294
|
|
|
224
295
|
let n = bytesToNumberBE(pathHash);
|
|
225
296
|
|
|
@@ -272,9 +343,8 @@ export type PlinkoResult = {
|
|
|
272
343
|
multiplier: number;
|
|
273
344
|
};
|
|
274
345
|
|
|
275
|
-
export function
|
|
276
|
-
|
|
277
|
-
clientSeed: string,
|
|
346
|
+
export function computePlinkoResultFromRandomSource(
|
|
347
|
+
randomSource: RandomSource,
|
|
278
348
|
payouts: number[]
|
|
279
349
|
): PlinkoResult {
|
|
280
350
|
if (payouts.length < 2) {
|
|
@@ -282,8 +352,7 @@ export function computePlinkoResult(
|
|
|
282
352
|
}
|
|
283
353
|
|
|
284
354
|
const probabilities = computePlinkoPascalsProbabilities(payouts.length);
|
|
285
|
-
const
|
|
286
|
-
const roll = uniformFromHash(rollHash);
|
|
355
|
+
const roll = uniformFromHash(randomSource);
|
|
287
356
|
const slot = pickSlot(probabilities, roll);
|
|
288
357
|
const multiplier = payouts[slot] ?? 0;
|
|
289
358
|
|
|
@@ -293,29 +362,51 @@ export function computePlinkoResult(
|
|
|
293
362
|
};
|
|
294
363
|
}
|
|
295
364
|
|
|
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);
|
|
372
|
+
}
|
|
373
|
+
|
|
296
374
|
function hmacSha256(key: Uint8Array, message: string): Uint8Array {
|
|
297
375
|
const data = new TextEncoder().encode(message);
|
|
298
376
|
return hmac(sha256, key, data);
|
|
299
377
|
}
|
|
300
378
|
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
379
|
+
function hmacSha256Bytes(key: Uint8Array, message: Uint8Array): Uint8Array {
|
|
380
|
+
return hmac(sha256, key, message);
|
|
381
|
+
}
|
|
305
382
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return
|
|
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
|
+
);
|
|
311
394
|
}
|
|
312
395
|
|
|
313
|
-
function
|
|
314
|
-
if (
|
|
315
|
-
throw new Error("
|
|
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.");
|
|
316
399
|
}
|
|
317
|
-
const
|
|
318
|
-
|
|
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);
|
|
319
410
|
return normalized / 2 ** 32;
|
|
320
411
|
}
|
|
321
412
|
|