@actuallyfair/verifier 0.0.1 → 0.0.3

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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PosthashPlinko = exports.PosthashDice = exports.PosthashInit = exports.PosthashContext = exports.Context = void 0;
6
+ exports.PosthashInit = exports.Context = void 0;
7
7
  /* eslint-disable */
8
8
  const minimal_1 = __importDefault(require("protobufjs/minimal"));
9
9
  const crash_1 = require("./crash");
@@ -24,6 +24,7 @@ function createBaseContext() {
24
24
  mines: undefined,
25
25
  tower: undefined,
26
26
  plinko: undefined,
27
+ init: undefined,
27
28
  };
28
29
  }
29
30
  exports.Context = {
@@ -52,6 +53,9 @@ exports.Context = {
52
53
  if (message.plinko !== undefined) {
53
54
  plinko_1.Plinko.encode(message.plinko, writer.uint32(66).fork()).ldelim();
54
55
  }
56
+ if (message.init !== undefined) {
57
+ exports.PosthashInit.encode(message.init, writer.uint32(74).fork()).ldelim();
58
+ }
55
59
  return writer;
56
60
  },
57
61
  decode(input, length) {
@@ -109,6 +113,12 @@ exports.Context = {
109
113
  }
110
114
  message.plinko = plinko_1.Plinko.decode(reader, reader.uint32());
111
115
  continue;
116
+ case 9:
117
+ if (tag !== 74) {
118
+ break;
119
+ }
120
+ message.init = exports.PosthashInit.decode(reader, reader.uint32());
121
+ continue;
112
122
  }
113
123
  if ((tag & 7) === 4 || tag === 0) {
114
124
  break;
@@ -127,6 +137,7 @@ exports.Context = {
127
137
  mines: isSet(object.mines) ? mines_1.Mines.fromJSON(object.mines) : undefined,
128
138
  tower: isSet(object.tower) ? tower_1.Tower.fromJSON(object.tower) : undefined,
129
139
  plinko: isSet(object.plinko) ? plinko_1.Plinko.fromJSON(object.plinko) : undefined,
140
+ init: isSet(object.init) ? exports.PosthashInit.fromJSON(object.init) : undefined,
130
141
  };
131
142
  },
132
143
  toJSON(message) {
@@ -155,6 +166,9 @@ exports.Context = {
155
166
  if (message.plinko !== undefined) {
156
167
  obj.plinko = plinko_1.Plinko.toJSON(message.plinko);
157
168
  }
169
+ if (message.init !== undefined) {
170
+ obj.init = exports.PosthashInit.toJSON(message.init);
171
+ }
158
172
  return obj;
159
173
  },
160
174
  create(base) {
@@ -178,92 +192,9 @@ exports.Context = {
178
192
  message.plinko = (object.plinko !== undefined && object.plinko !== null)
179
193
  ? plinko_1.Plinko.fromPartial(object.plinko)
180
194
  : undefined;
181
- return message;
182
- },
183
- };
184
- function createBasePosthashContext() {
185
- return { init: undefined, dice: undefined, plinko: undefined };
186
- }
187
- exports.PosthashContext = {
188
- encode(message, writer = minimal_1.default.Writer.create()) {
189
- if (message.init !== undefined) {
190
- exports.PosthashInit.encode(message.init, writer.uint32(10).fork()).ldelim();
191
- }
192
- if (message.dice !== undefined) {
193
- exports.PosthashDice.encode(message.dice, writer.uint32(18).fork()).ldelim();
194
- }
195
- if (message.plinko !== undefined) {
196
- exports.PosthashPlinko.encode(message.plinko, writer.uint32(26).fork()).ldelim();
197
- }
198
- return writer;
199
- },
200
- decode(input, length) {
201
- const reader = input instanceof minimal_1.default.Reader ? input : minimal_1.default.Reader.create(input);
202
- let end = length === undefined ? reader.len : reader.pos + length;
203
- const message = createBasePosthashContext();
204
- while (reader.pos < end) {
205
- const tag = reader.uint32();
206
- switch (tag >>> 3) {
207
- case 1:
208
- if (tag !== 10) {
209
- break;
210
- }
211
- message.init = exports.PosthashInit.decode(reader, reader.uint32());
212
- continue;
213
- case 2:
214
- if (tag !== 18) {
215
- break;
216
- }
217
- message.dice = exports.PosthashDice.decode(reader, reader.uint32());
218
- continue;
219
- case 3:
220
- if (tag !== 26) {
221
- break;
222
- }
223
- message.plinko = exports.PosthashPlinko.decode(reader, reader.uint32());
224
- continue;
225
- }
226
- if ((tag & 7) === 4 || tag === 0) {
227
- break;
228
- }
229
- reader.skipType(tag & 7);
230
- }
231
- return message;
232
- },
233
- fromJSON(object) {
234
- return {
235
- init: isSet(object.init) ? exports.PosthashInit.fromJSON(object.init) : undefined,
236
- dice: isSet(object.dice) ? exports.PosthashDice.fromJSON(object.dice) : undefined,
237
- plinko: isSet(object.plinko) ? exports.PosthashPlinko.fromJSON(object.plinko) : undefined,
238
- };
239
- },
240
- toJSON(message) {
241
- const obj = {};
242
- if (message.init !== undefined) {
243
- obj.init = exports.PosthashInit.toJSON(message.init);
244
- }
245
- if (message.dice !== undefined) {
246
- obj.dice = exports.PosthashDice.toJSON(message.dice);
247
- }
248
- if (message.plinko !== undefined) {
249
- obj.plinko = exports.PosthashPlinko.toJSON(message.plinko);
250
- }
251
- return obj;
252
- },
253
- create(base) {
254
- return exports.PosthashContext.fromPartial(base ?? {});
255
- },
256
- fromPartial(object) {
257
- const message = createBasePosthashContext();
258
195
  message.init = (object.init !== undefined && object.init !== null)
259
196
  ? exports.PosthashInit.fromPartial(object.init)
260
197
  : undefined;
261
- message.dice = (object.dice !== undefined && object.dice !== null)
262
- ? exports.PosthashDice.fromPartial(object.dice)
263
- : undefined;
264
- message.plinko = (object.plinko !== undefined && object.plinko !== null)
265
- ? exports.PosthashPlinko.fromPartial(object.plinko)
266
- : undefined;
267
198
  return message;
268
199
  },
269
200
  };
@@ -304,209 +235,6 @@ exports.PosthashInit = {
304
235
  return message;
305
236
  },
306
237
  };
307
- function createBasePosthashDice() {
308
- return { target: 0, clientSeed: "", hashchainServerHash: new Uint8Array(0) };
309
- }
310
- exports.PosthashDice = {
311
- encode(message, writer = minimal_1.default.Writer.create()) {
312
- if (message.target !== 0) {
313
- writer.uint32(9).double(message.target);
314
- }
315
- if (message.clientSeed !== "") {
316
- writer.uint32(18).string(message.clientSeed);
317
- }
318
- if (message.hashchainServerHash.length !== 0) {
319
- writer.uint32(26).bytes(message.hashchainServerHash);
320
- }
321
- return writer;
322
- },
323
- decode(input, length) {
324
- const reader = input instanceof minimal_1.default.Reader ? input : minimal_1.default.Reader.create(input);
325
- let end = length === undefined ? reader.len : reader.pos + length;
326
- const message = createBasePosthashDice();
327
- while (reader.pos < end) {
328
- const tag = reader.uint32();
329
- switch (tag >>> 3) {
330
- case 1:
331
- if (tag !== 9) {
332
- break;
333
- }
334
- message.target = reader.double();
335
- continue;
336
- case 2:
337
- if (tag !== 18) {
338
- break;
339
- }
340
- message.clientSeed = reader.string();
341
- continue;
342
- case 3:
343
- if (tag !== 26) {
344
- break;
345
- }
346
- message.hashchainServerHash = reader.bytes();
347
- continue;
348
- }
349
- if ((tag & 7) === 4 || tag === 0) {
350
- break;
351
- }
352
- reader.skipType(tag & 7);
353
- }
354
- return message;
355
- },
356
- fromJSON(object) {
357
- return {
358
- target: isSet(object.target) ? globalThis.Number(object.target) : 0,
359
- clientSeed: isSet(object.clientSeed) ? globalThis.String(object.clientSeed) : "",
360
- hashchainServerHash: isSet(object.hashchainServerHash)
361
- ? bytesFromBase64(object.hashchainServerHash)
362
- : new Uint8Array(0),
363
- };
364
- },
365
- toJSON(message) {
366
- const obj = {};
367
- if (message.target !== 0) {
368
- obj.target = message.target;
369
- }
370
- if (message.clientSeed !== "") {
371
- obj.clientSeed = message.clientSeed;
372
- }
373
- if (message.hashchainServerHash.length !== 0) {
374
- obj.hashchainServerHash = base64FromBytes(message.hashchainServerHash);
375
- }
376
- return obj;
377
- },
378
- create(base) {
379
- return exports.PosthashDice.fromPartial(base ?? {});
380
- },
381
- fromPartial(object) {
382
- const message = createBasePosthashDice();
383
- message.target = object.target ?? 0;
384
- message.clientSeed = object.clientSeed ?? "";
385
- message.hashchainServerHash = object.hashchainServerHash ?? new Uint8Array(0);
386
- return message;
387
- },
388
- };
389
- function createBasePosthashPlinko() {
390
- return { rows: 0, riskLevel: "", clientSeed: "", hashchainServerHash: new Uint8Array(0) };
391
- }
392
- exports.PosthashPlinko = {
393
- encode(message, writer = minimal_1.default.Writer.create()) {
394
- if (message.rows !== 0) {
395
- writer.uint32(8).uint32(message.rows);
396
- }
397
- if (message.riskLevel !== "") {
398
- writer.uint32(18).string(message.riskLevel);
399
- }
400
- if (message.clientSeed !== "") {
401
- writer.uint32(26).string(message.clientSeed);
402
- }
403
- if (message.hashchainServerHash.length !== 0) {
404
- writer.uint32(34).bytes(message.hashchainServerHash);
405
- }
406
- return writer;
407
- },
408
- decode(input, length) {
409
- const reader = input instanceof minimal_1.default.Reader ? input : minimal_1.default.Reader.create(input);
410
- let end = length === undefined ? reader.len : reader.pos + length;
411
- const message = createBasePosthashPlinko();
412
- while (reader.pos < end) {
413
- const tag = reader.uint32();
414
- switch (tag >>> 3) {
415
- case 1:
416
- if (tag !== 8) {
417
- break;
418
- }
419
- message.rows = reader.uint32();
420
- continue;
421
- case 2:
422
- if (tag !== 18) {
423
- break;
424
- }
425
- message.riskLevel = reader.string();
426
- continue;
427
- case 3:
428
- if (tag !== 26) {
429
- break;
430
- }
431
- message.clientSeed = reader.string();
432
- continue;
433
- case 4:
434
- if (tag !== 34) {
435
- break;
436
- }
437
- message.hashchainServerHash = reader.bytes();
438
- continue;
439
- }
440
- if ((tag & 7) === 4 || tag === 0) {
441
- break;
442
- }
443
- reader.skipType(tag & 7);
444
- }
445
- return message;
446
- },
447
- fromJSON(object) {
448
- return {
449
- rows: isSet(object.rows) ? globalThis.Number(object.rows) : 0,
450
- riskLevel: isSet(object.riskLevel) ? globalThis.String(object.riskLevel) : "",
451
- clientSeed: isSet(object.clientSeed) ? globalThis.String(object.clientSeed) : "",
452
- hashchainServerHash: isSet(object.hashchainServerHash)
453
- ? bytesFromBase64(object.hashchainServerHash)
454
- : new Uint8Array(0),
455
- };
456
- },
457
- toJSON(message) {
458
- const obj = {};
459
- if (message.rows !== 0) {
460
- obj.rows = Math.round(message.rows);
461
- }
462
- if (message.riskLevel !== "") {
463
- obj.riskLevel = message.riskLevel;
464
- }
465
- if (message.clientSeed !== "") {
466
- obj.clientSeed = message.clientSeed;
467
- }
468
- if (message.hashchainServerHash.length !== 0) {
469
- obj.hashchainServerHash = base64FromBytes(message.hashchainServerHash);
470
- }
471
- return obj;
472
- },
473
- create(base) {
474
- return exports.PosthashPlinko.fromPartial(base ?? {});
475
- },
476
- fromPartial(object) {
477
- const message = createBasePosthashPlinko();
478
- message.rows = object.rows ?? 0;
479
- message.riskLevel = object.riskLevel ?? "";
480
- message.clientSeed = object.clientSeed ?? "";
481
- message.hashchainServerHash = object.hashchainServerHash ?? new Uint8Array(0);
482
- return message;
483
- },
484
- };
485
- function bytesFromBase64(b64) {
486
- if (globalThis.Buffer) {
487
- return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
488
- }
489
- else {
490
- const bin = globalThis.atob(b64);
491
- const arr = new Uint8Array(bin.length);
492
- for (let i = 0; i < bin.length; ++i) {
493
- arr[i] = bin.charCodeAt(i);
494
- }
495
- return arr;
496
- }
497
- }
498
- function base64FromBytes(arr) {
499
- if (globalThis.Buffer) {
500
- return globalThis.Buffer.from(arr).toString("base64");
501
- }
502
- else {
503
- const bin = [];
504
- arr.forEach((byte) => {
505
- bin.push(globalThis.String.fromCharCode(byte));
506
- });
507
- return globalThis.btoa(bin.join(""));
508
- }
509
- }
510
238
  function isSet(value) {
511
239
  return value !== null && value !== undefined;
512
240
  }
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@actuallyfair/verifier",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "scripts": {
8
8
  "clean": "rm -rf dist && mkdir dist && rm -rf src/generated && mkdir src/generated",
9
9
  "build": "npm run clean && npm run generate && tsc",
10
- "generate": "protoc --proto_path=./protobuf --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt=esModuleInterop=true --ts_proto_opt=exportCommonSymbols=false --ts_proto_out=./src/generated ./protobuf/context/index.proto ./protobuf/currency.proto",
10
+ "generate": "protoc --proto_path=./protobuf --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt=esModuleInterop=true --ts_proto_opt=exportCommonSymbols=false --ts_proto_out=./src/generated ./protobuf/context/*.proto ./protobuf/amount.proto ./protobuf/currency.proto",
11
+ "decode": "tsx ./src/cli/context-codec.ts decode",
12
+ "encode": "tsx ./src/cli/context-codec.ts encode",
11
13
  "prepublishOnly": "npm run build"
12
14
  },
13
15
  "publishConfig": {
@@ -4,9 +4,9 @@ import "context/fair-coin-toss.proto";
4
4
  import "context/crash.proto";
5
5
  import "context/hilo.proto";
6
6
  import "context/crash-dice.proto";
7
+ import "context/multi-roulette.proto";
7
8
  import "context/mines.proto";
8
9
  import "context/tower.proto";
9
- import "context/multi-roulette.proto";
10
10
  import "context/plinko.proto";
11
11
 
12
12
  message Context {
@@ -19,28 +19,8 @@ message Context {
19
19
  Mines mines = 6;
20
20
  Tower tower = 7;
21
21
  Plinko plinko = 8;
22
- }
23
- }
24
-
25
- message PosthashContext {
26
- oneof payload {
27
- PosthashInit init = 1;
28
- PosthashDice dice = 2;
29
- PosthashPlinko plinko = 3;
22
+ PosthashInit init = 9;
30
23
  }
31
24
  }
32
25
 
33
26
  message PosthashInit {}
34
-
35
- message PosthashDice {
36
- double target = 1;
37
- string client_seed = 2;
38
- bytes hashchain_server_hash = 3;
39
- }
40
-
41
- message PosthashPlinko {
42
- uint32 rows = 1;
43
- string risk_level = 2;
44
- string client_seed = 3;
45
- bytes hashchain_server_hash = 4;
46
- }
@@ -0,0 +1,103 @@
1
+ import { Context } from "../generated/context";
2
+
3
+ type Mode = "decode" | "encode";
4
+
5
+ function isMode(value: string | undefined): value is Mode {
6
+ return value === "decode" || value === "encode";
7
+ }
8
+
9
+ function normalizeBase64(input: string): string {
10
+ const cleaned = input.replace(/\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");
11
+ const padding = cleaned.length % 4;
12
+ return padding === 0 ? cleaned : `${cleaned}${"=".repeat(4 - padding)}`;
13
+ }
14
+
15
+ function readStdin(): Promise<string> {
16
+ return new Promise((resolve, reject) => {
17
+ let data = "";
18
+ process.stdin.setEncoding("utf8");
19
+ process.stdin.on("data", (chunk) => {
20
+ data += chunk;
21
+ });
22
+ process.stdin.on("end", () => resolve(data));
23
+ process.stdin.on("error", reject);
24
+ });
25
+ }
26
+
27
+ async function readInput(args: string[]): Promise<string> {
28
+ if (args.length > 0) {
29
+ return args.join(" ").trim();
30
+ }
31
+
32
+ if (process.stdin.isTTY) {
33
+ return "";
34
+ }
35
+
36
+ const data = await readStdin();
37
+ return data.trim();
38
+ }
39
+
40
+ function usage(mode?: Mode): string[] {
41
+ if (mode === "decode") {
42
+ return ["Usage: npm run decode -- <base64>", " cat context.b64 | npm run decode"];
43
+ }
44
+ if (mode === "encode") {
45
+ return ["Usage: npm run encode -- '<json>'", " cat context.json | npm run encode"];
46
+ }
47
+ return [
48
+ "Usage: npm run decode -- <base64>",
49
+ " npm run encode -- '<json>'",
50
+ " cat context.b64 | npm run decode",
51
+ " cat context.json | npm run encode",
52
+ ];
53
+ }
54
+
55
+ function printUsage(mode?: Mode): void {
56
+ for (const line of usage(mode)) {
57
+ console.error(line);
58
+ }
59
+ }
60
+
61
+ function errorMessage(error: unknown): string {
62
+ if (error instanceof Error) {
63
+ return error.message;
64
+ }
65
+ return String(error);
66
+ }
67
+
68
+ async function main(): Promise<void> {
69
+ const [modeRaw, ...rest] = process.argv.slice(2);
70
+ if (!isMode(modeRaw)) {
71
+ printUsage();
72
+ process.exit(1);
73
+ return;
74
+ }
75
+
76
+ const input = await readInput(rest);
77
+ if (!input) {
78
+ printUsage(modeRaw);
79
+ process.exit(1);
80
+ return;
81
+ }
82
+
83
+ try {
84
+ if (modeRaw === "decode") {
85
+ const base64 = normalizeBase64(input);
86
+ const bytes = Buffer.from(base64, "base64");
87
+ const message = Context.decode(bytes);
88
+ const json = Context.toJSON(message);
89
+ process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
90
+ return;
91
+ }
92
+
93
+ const parsed = JSON.parse(input) as unknown;
94
+ const message = Context.fromJSON(parsed);
95
+ const encoded = Context.encode(message).finish();
96
+ process.stdout.write(`${Buffer.from(encoded).toString("base64")}\n`);
97
+ } catch (error) {
98
+ console.error(`Failed to ${modeRaw} context: ${errorMessage(error)}`);
99
+ process.exit(1);
100
+ }
101
+ }
102
+
103
+ void main();
@@ -12,7 +12,9 @@ import {
12
12
  FairCoinToss_Choice,
13
13
  } from "./generated/context/fair-coin-toss";
14
14
  import { bytesToHex } from "@noble/hashes/utils";
15
+ import { CrashDice } from "./generated/context/crash-dice";
15
16
  import { MultiRoulette } from "./generated/context/multi-roulette";
17
+ import { Plinko } from "./generated/context/plinko";
16
18
 
17
19
  export function computeFairCoinTossResult(sig: Uint8Array) {
18
20
  // We're going to hash the signature just to really be sure its fairly distributed
@@ -65,8 +67,30 @@ export function computeCrashResult(
65
67
  return doComputeCrashResult(hmac(sha256, vxSignature, gameHash), houseEdge);
66
68
  }
67
69
 
68
- export function computeCrashDiceResult(sig: Uint8Array, houseEdge: number) {
69
- return doComputeCrashResult(sha256(sig), houseEdge);
70
+ export type CrashDiceOutcome = {
71
+ multiplier: number;
72
+ target: number;
73
+ win: boolean;
74
+ };
75
+
76
+ export function computeCrashDiceResult(hash: Uint8Array, clientSeed: string) {
77
+ const rollHash = hmacSha256(hash, clientSeed);
78
+ return multiplierFromHash(rollHash);
79
+ }
80
+
81
+ export function computeCrashDiceOutcome(
82
+ hash: Uint8Array,
83
+ clientSeed: string,
84
+ bet: CrashDice
85
+ ): CrashDiceOutcome {
86
+ const multiplier = computeCrashDiceResult(hash, clientSeed);
87
+ const target = bet.target;
88
+
89
+ return {
90
+ multiplier,
91
+ target,
92
+ win: multiplier >= target,
93
+ };
70
94
  }
71
95
 
72
96
  // returns the index of which roulette outcome was picked
@@ -243,3 +267,69 @@ export function computePlinkoHouseEdge(possibilities: number[]) {
243
267
  }
244
268
  return ev;
245
269
  }
270
+
271
+ export type PlinkoResult = {
272
+ slot: number;
273
+ multiplier: number;
274
+ win: boolean;
275
+ };
276
+
277
+ export function computePlinkoResult(
278
+ hash: Uint8Array,
279
+ clientSeed: string,
280
+ bet: Plinko
281
+ ): PlinkoResult {
282
+ const possibilities = bet.possibilities;
283
+ if (possibilities.length < 2) {
284
+ throw new Error("invalid possibilities ");
285
+ }
286
+
287
+ const probabilities = computePlinkoPascalsProbabilities(possibilities.length);
288
+ const rollHash = hmacSha256(hash, clientSeed);
289
+ const roll = uniformFromHash(rollHash);
290
+ const slot = pickSlot(probabilities, roll);
291
+ const multiplier = possibilities[slot] ?? 0;
292
+
293
+ return {
294
+ slot,
295
+ multiplier,
296
+ win: multiplier >= 1,
297
+ };
298
+ }
299
+
300
+ function hmacSha256(key: Uint8Array, message: string): Uint8Array {
301
+ const data = new TextEncoder().encode(message);
302
+ return hmac(sha256, key, data);
303
+ }
304
+
305
+ function multiplierFromHash(hash: Uint8Array): number {
306
+ if (hash.length < 4) {
307
+ throw new Error("Hash must be at least 4 bytes.");
308
+ }
309
+
310
+ const value = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
311
+ const normalized = value >>> 0;
312
+ const max = 2 ** 32;
313
+ const multiplierTimes100 = Math.floor((100 * max) / (max - normalized));
314
+ return multiplierTimes100 / 100;
315
+ }
316
+
317
+ function uniformFromHash(hash: Uint8Array): number {
318
+ if (hash.length < 4) {
319
+ throw new Error("Hash must be at least 4 bytes.");
320
+ }
321
+ const value = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
322
+ const normalized = value >>> 0;
323
+ return normalized / 2 ** 32;
324
+ }
325
+
326
+ function pickSlot(probabilities: number[], roll: number): number {
327
+ let cumulative = 0;
328
+ for (let i = 0; i < probabilities.length; i += 1) {
329
+ cumulative += probabilities[i];
330
+ if (roll <= cumulative) {
331
+ return i;
332
+ }
333
+ }
334
+ return probabilities.length - 1;
335
+ }