24 | `;
25 | const root = this.attachShadow({ mode: 'open' });
26 | root.appendChild(template.content.cloneNode(true));
27 | }
28 | }
29 | );
30 |
--------------------------------------------------------------------------------
/public/javascripts/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate a random string with a specified length.
3 | * @param length - String length
4 | * @returns {string}
5 | */
6 | const randomString = length => {
7 | const availableChars =
8 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
9 | let randomString = '';
10 | for (let i = 0; i < length; i++) {
11 | randomString +=
12 | availableChars[Math.floor(Math.random() * availableChars.length)];
13 | }
14 | return randomString;
15 | };
16 |
17 | const getClientSeed = () => {
18 | // try to get the client seed from local storage
19 | let clientSeed = localStorage.getItem('client-seed');
20 | // if there isn't a client seed set on the client
21 | if (!clientSeed) {
22 | // generate a new client seed
23 | clientSeed = randomString(20);
24 | // set the generated client seed to local storage so it will be stored opn reloading
25 | localStorage.setItem('client-seed', clientSeed);
26 | }
27 | return clientSeed;
28 | };
29 | const getHashedServerSeed = async () => await $.ajax('/api/hashed-server-seed');
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Alexandru Cambose
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Provably fair example
2 |
3 | A provably fair example build with express, and plain javascript.
4 |
5 | ### What is **provably fair**
6 |
7 | A provably fair algorithm allows users to check and analyze the legitimacy of every roll and confirm that the server/house did not alter them in their favor. By using a provably fair algorithm to generate random numbers neither players nor the operator of the site can know the result of the game before it starts.
8 |
9 | For more information about the techincal details of the implementation check out this article
10 |
11 | [Provably fair system in javascript - What is and how to build a provably fair system in javascript](https://medium.com/@alexcambose/provably-fair-system-in-javascript-6457e028d2aa)
12 |
13 | ## Getting Started
14 |
15 | ### Prerequisites
16 |
17 | Nodejs 8+ installed
18 |
19 | ### Installation
20 |
21 | [Clone](https://git-scm.com/docs/git-clone) this repo
22 |
23 | ```
24 | $ npm install
25 | ```
26 |
27 | ## How to use?
28 |
29 | ```
30 | $ npm run start
31 | ```
32 |
33 | Open you favourite browser on [localhost:3000](http://localhost:3000/).
34 |
35 | ## License
36 |
37 | This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details
38 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 | /**
3 | * Hashed a string
4 | * @param string
5 | * @returns {string}
6 | */
7 | const sha512 = string =>
8 | crypto
9 | .createHash('sha512')
10 | .update(string)
11 | .digest('hex');
12 | /**
13 | * Generate a 256 characters string
14 | * @returns {string}
15 | */
16 | const generateServerSeed = () => crypto.randomBytes(256).toString('hex');
17 | /**
18 | * Custom function for concatenating server-seed, client-seed and nonce.
19 | * @param serverSeed
20 | * @param clientSeed
21 | * @param nonce
22 | * @returns {*}
23 | */
24 | const combine = (serverSeed, clientSeed, nonce) =>
25 | serverSeed + clientSeed + nonce;
26 | /**
27 | * Converts a hex hash into a number between [0, 99]
28 | * @param hashedValue
29 | * @returns {*}
30 | */
31 | const getResult = hashedValue => {
32 | // the offset of the interval
33 | let index = 0;
34 | // result variable
35 | let result;
36 |
37 | do {
38 | // get the decimal value from an interval of 5 hex letters
39 | result = parseInt(hashedValue.substring(index * 5, index * 5 + 5), 16);
40 | // increment the offset in case we will need to repeat the operation above
41 | index += 1;
42 | // if all the numbers were over 999999 and we reached the end of the string, we set that to a default value of 9999 (99 as a result)
43 | if (index * 5 + 5 > 129) {
44 | result = 9999;
45 | break;
46 | }
47 | } while (result >= 1e6);
48 | // the result is between 0-999999 and we need to convert if into a 4 digit number
49 | // we a apply a modulus of 1000 and the 4 digit number is further split into a 2 digit number with decimals
50 | return [result % 1e4] * 1e-2;
51 | };
52 |
53 | module.exports = { generateServerSeed, sha512, combine, getResult };
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment constiables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # next.js build output
79 | .next
80 |
81 | # nuxt.js build output
82 | .nuxt
83 |
84 | # gatsby files
85 | .cache/
86 | #public
87 |
88 | # vuepress build output
89 | .vuepress/dist
90 |
91 | # Serverless directories
92 | .serverless/
93 |
94 | # FuseBox cache
95 | .fusebox/
96 |
97 | # DynamoDB Local files
98 | .dynamodb/
99 | .idea
100 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | const app = require('../app');
8 | const debug = require('debug')('provably-fair-example:server');
9 | const http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | const port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | const server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | const port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
62 |
63 | // handle specific listen errors with friendly messages
64 | switch (error.code) {
65 | case 'EACCES':
66 | console.error(bind + ' requires elevated privileges');
67 | process.exit(1);
68 | break;
69 | case 'EADDRINUSE':
70 | console.error(bind + ' is already in use');
71 | process.exit(1);
72 | break;
73 | default:
74 | throw error;
75 | }
76 | }
77 |
78 | /**
79 | * Event listener for HTTP server "listening" event.
80 | */
81 |
82 | function onListening() {
83 | const addr = server.address();
84 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
85 | debug('Listening on ' + bind);
86 | }
87 |
--------------------------------------------------------------------------------
/public/javascripts/script.js:
--------------------------------------------------------------------------------
1 | $(document).ready(async () => {
2 | // get hashed server seed when the page is loading for the first time
3 | const hashedServerSeed = await getHashedServerSeed();
4 | // get client seed for further usages
5 | let clientSeed = getClientSeed();
6 |
7 | // seleting general page elements
8 | const $hashedServerSeedBox = $('#hashed-server-seed');
9 | const $clientSeedInput = $('#client-seed-input');
10 | const $verifyClientSeedInput = $('#verify-client-seed-input');
11 | const $verifyNonceInput = $('#verify-nonce-input');
12 | const $verifyServerSeedInput = $('#verify-server-seed-input');
13 | const $rollResultsContainer = $('#roll-results-container');
14 | const $rollResult = $('#roll-result');
15 | const $serverSeed = $('#server-seed');
16 | const $nonce = $('#nonce');
17 | const $verifyRollResultsContainer = $('#verify-roll-results-container');
18 | const $verifyRollResult = $('#verify-roll-result');
19 | // set hashed server seed so that the client can see this before rolling
20 | $hashedServerSeedBox.text(hashedServerSeed);
21 | // set client seed content
22 | $clientSeedInput.val(clientSeed);
23 | $verifyClientSeedInput.val(clientSeed);
24 | // add on click event to the roll button
25 | $('#roll-buton').on('click', async () => {
26 | const { result, serverSeed, nonce } = await $.ajax({
27 | url: '/api/result',
28 | data: {
29 | clientSeed,
30 | },
31 | });
32 | $rollResultsContainer.show();
33 | $rollResult.text(result);
34 | $serverSeed.text(serverSeed);
35 | $nonce.text(nonce);
36 | // get and update the server seed box, you can also include this data in the resutl api call so an additional request can be saved
37 | $hashedServerSeedBox.text(await getHashedServerSeed());
38 | });
39 | // add on submit event for the verify roll foerm
40 | $('#verify-bet-form').on('submit', async e => {
41 | e.preventDefault();
42 | // get values from inputs
43 | const clientSeed = $verifyClientSeedInput.val();
44 | const nonce = $verifyNonceInput.val();
45 | const serverSeed = $verifyServerSeedInput.val();
46 | // create an ajax request to the server to compute the values provided into a result
47 | const result = await $.ajax({
48 | url: '/api/verify',
49 | data: {
50 | clientSeed,
51 | nonce,
52 | serverSeed,
53 | },
54 | });
55 | // update html elements to displat the returned result
56 | $verifyRollResultsContainer.show();
57 | $verifyRollResult.text(result);
58 | });
59 | // add event handler for the client seed input
60 | $clientSeedInput.on('change', e => {
61 | // update the client seed with the input value when it changes
62 | clientSeed = e.target.value;
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/routes/api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { generateServerSeed, sha512, combine, getResult } = require('../utils');
4 | // _These are for illustration purposes only_, you shouldn't store values on a node instance as these
5 | // values will be erased when the server is closed and also it doesn't allow for more that one user.
6 |
7 | // Ideally the each user should have their own server-seed stored in their profile and the nonce will
8 | // be the number of the results + 1
9 |
10 | let results = [];
11 | const user = {
12 | serverSeed: '',
13 | };
14 | /**
15 | * This route is called before the client is creating a new roll,
16 | * it returns a hashed version of the server seed. By using a hashed version
17 | * of it, the client can make sure that this is not going to be changed in
18 | * the future but also cannot get it's initial value (because it's hashed).
19 | * It doesn't return the hashed serverseed in advance, because that would
20 | * allow the client to generate all the rolls in advance.
21 | */
22 | router.get('/hashed-server-seed', function(req, res, next) {
23 | // save the server seed into the users "table"
24 | user.serverSeed = generateServerSeed();
25 | // return a hashed version of the server seed so the client can verify that it wasn't altered
26 | res.send(sha512(user.serverSeed));
27 | });
28 |
29 | /**
30 | * When called, a random number gets generated
31 | * Query parameters:
32 | * clientSeed - String - A random sequence of characters generated by the client
33 | */
34 | router.get('/result', function(req, res, next) {
35 | // get current server seed specific to the current user
36 | const { serverSeed } = user;
37 | // get client seed from the request query parameters, the client seed is provided by the client's browser
38 | const { clientSeed } = req.query;
39 | // get nonce by incrementing the number of results
40 | const nonce = results.length + 1;
41 | // RESULT GENERATION
42 | // combine values -> append them to one another, or your custom rule of combining them
43 | const combination = combine(serverSeed, clientSeed, nonce);
44 | // create a result object
45 | const newResult = {
46 | result: getResult(sha512(combination)),
47 | serverSeed,
48 | nonce,
49 | };
50 | // NEW SERVER SEED
51 | // generate a new server seed
52 | user.serverSeed = generateServerSeed();
53 | // RESULT RETURNING
54 | // insert into our "database" the new result
55 | results.push(newResult);
56 | // return values, the result and the plain server seed with nonce
57 | res.json(newResult);
58 | });
59 | /**
60 | * The client can now take this value and check that the result did not
61 | * change after the roll was generated.
62 | *
63 | * Query Parameters:
64 | * clientSeed - String - The client seed that was used to generate the result
65 | * serverSeed - String - The server seed that was used to generate the result
66 | * nonce - Number - The nonce of the result
67 | */
68 | router.get('/verify', function(req, res, next) {
69 | // get the data provided by the client
70 | const { clientSeed, serverSeed, nonce } = req.query;
71 | // create a combination from the provided data
72 | const combination = combine(serverSeed, clientSeed, nonce);
73 | // generate a result from the data
74 | const result = getResult(sha512(combination));
75 | // return to client
76 | res.send(String(result));
77 | });
78 | module.exports = router;
79 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Express
4 |
5 |
9 |
13 |
14 |
15 |
16 |