From 46400771fa71bf782346804e75eb729a8aef73cd Mon Sep 17 00:00:00 2001 From: Francois Laberge Date: Mon, 28 Nov 2016 17:43:22 -0500 Subject: [PATCH] Added scramble/descramble for more easily skip to different points in the random series of number the generator generates (#3) --- LICENSE | 3 ++ README.md | 22 ++++++++++++ RESEARCH.md | 4 +++ src/Skip32PureJS.js | 88 +++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 5 ++- src/scrambler.js | 24 +++++++++++++ tests/index.js | 59 +++++++++++++++++++++++++----- 7 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 src/Skip32PureJS.js create mode 100644 src/scrambler.js diff --git a/LICENSE b/LICENSE index e769393..fa791fe 100644 --- a/LICENSE +++ b/LICENSE @@ -2,6 +2,9 @@ Copyright (c) 2016 Francois Laberge +NOTE: The Skip32PureJS.js source is entirely public domain, see it's header comment +for details. + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including diff --git a/README.md b/README.md index c4b25df..635f98c 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,28 @@ console.log( generate.prev.number(-10, 10) ); console.log( generate.prev.number(0, 10000) ); ``` +## Scramble / Descramble +The scramble function is best used for turning sequences of ordered numbers like a series of increasing numbers +and scrambling the bits to get a pseudo random number. It also reversible via `arbitrary.descramble()`. + +**Important**: Use Generator .next/.prev if +you don't need this focus but just want a series of random numbers as this scramble/unscramble are computationally +more expensive. + +```js +import arbitrary from 'arbitrary'; + +// Scramble the bits of an unsigned 32 bit integer +const scrambled42 = arbitrary.scramble(42); + // Should print '1077848774' +console.log(scrambled42); + +// Reverse the scrambling to get back the original number +const unscrambled42 = arbitrary.descramble(scrambled42); + // Should print '42' +console.log(unscrambled42) +``` + ## API Reference Coming soon. See examples above. diff --git a/RESEARCH.md b/RESEARCH.md index d6c53bb..2384805 100644 --- a/RESEARCH.md +++ b/RESEARCH.md @@ -8,6 +8,7 @@ TODO unbiased in a direction as possible in generators such as (inclusive of both min/max) [-1,1] that want values between - Run exhaustive tests? (Maybe separately from main tests) + - Run for both next/prev but also scramble/unscramble - Prelimary tests of calling generate.next 2^32 times took 70s, so it's entirely feasible to do exhaustive tests of properties - Note: To ensure every value was hit and only once: `new Array(Math.pow(2,32))` fails, @@ -16,7 +17,10 @@ TODO - [Fisher-Yates Shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) - As found in here Mike Bostock's [Visualizing Algorithms](https://bost.ocks.org/mike/algorithms/#sorting) + - Find a good two way 32 bit hash + - [This conversation looks very promising, lots of leads](https://www.quora.com/How-do-you-symmetrically-encrypt-32-bit-auto-increment-IDs-to-avoid-using-64-bit-UUIDs-to-conceal-the-size-and-order-of-a-database-table) + - https://github.com/0x4139/node-skip32 - https://stackoverflow.com/questions/959916/way-to-encrypt-a-single-int - ISAAC - https://www.npmjs.com/package/isaac diff --git a/src/Skip32PureJS.js b/src/Skip32PureJS.js new file mode 100644 index 0000000..5a338e0 --- /dev/null +++ b/src/Skip32PureJS.js @@ -0,0 +1,88 @@ +/* + **Francois**: + Based on an implementation that's based on further implementations, most significant + change is removing the use of Buffers and ArrayBuffers. Original implementation was forked + here (hopefully the original repo still exists, to play it safe I've forked it) + https://github.com/francoislaberge/node-skip32 + + ** Original Comments in skip32.js's implementation ** + Skip32PureJS.js - public domain javascript implementation of: + + SKIP32 -- 32 bit block cipher based on SKIPJACK. + Written by Greg Rose, QUALCOMM Australia, 1999/04/27. + + In common: F-table, G-permutation, key schedule. + Different: 24 round feistel structure. + Based on: Unoptimized test implementation of SKIPJACK algorithm + Panu Rissanen + + SKIPJACK and KEA Algorithm Specifications + Version 2.0 + 29 May 1998 + + Not copyright, no rights reserved. +*/ +function Skip32() { + // Francois: I have modified the code to have a hardcoded key. values + // were taken from the example code in the node-skip32 project: + // https://github.com/0x4139/node-skip32#example + this.key = [0x9b, 0x21, 0x96, 0xe, 0x1a, 0xcf, 0x24, 0x5f, 0x14, 0x93]; +}; + +Skip32.prototype.init = function(){ +}; + +// Francois: I have modified the code to remove the use of Buffers and ArrayBuffers +// so that the code is more portable to other browsers and environments +const ftable = [ + 0xa3,0xd7,0x09,0x83,0xf8,0x48,0xf6,0xf4,0xb3,0x21,0x15,0x78,0x99,0xb1,0xaf,0xf9, + 0xe7,0x2d,0x4d,0x8a,0xce,0x4c,0xca,0x2e,0x52,0x95,0xd9,0x1e,0x4e,0x38,0x44,0x28, + 0x0a,0xdf,0x02,0xa0,0x17,0xf1,0x60,0x68,0x12,0xb7,0x7a,0xc3,0xe9,0xfa,0x3d,0x53, + 0x96,0x84,0x6b,0xba,0xf2,0x63,0x9a,0x19,0x7c,0xae,0xe5,0xf5,0xf7,0x16,0x6a,0xa2, + 0x39,0xb6,0x7b,0x0f,0xc1,0x93,0x81,0x1b,0xee,0xb4,0x1a,0xea,0xd0,0x91,0x2f,0xb8, + 0x55,0xb9,0xda,0x85,0x3f,0x41,0xbf,0xe0,0x5a,0x58,0x80,0x5f,0x66,0x0b,0xd8,0x90, + 0x35,0xd5,0xc0,0xa7,0x33,0x06,0x65,0x69,0x45,0x00,0x94,0x56,0x6d,0x98,0x9b,0x76, + 0x97,0xfc,0xb2,0xc2,0xb0,0xfe,0xdb,0x20,0xe1,0xeb,0xd6,0xe4,0xdd,0x47,0x4a,0x1d, + 0x42,0xed,0x9e,0x6e,0x49,0x3c,0xcd,0x43,0x27,0xd2,0x07,0xd4,0xde,0xc7,0x67,0x18, + 0x89,0xcb,0x30,0x1f,0x8d,0xc6,0x8f,0xaa,0xc8,0x74,0xdc,0xc9,0x5d,0x5c,0x31,0xa4, + 0x70,0x88,0x61,0x2c,0x9f,0x0d,0x2b,0x87,0x50,0x82,0x54,0x64,0x26,0x7d,0x03,0x40, + 0x34,0x4b,0x1c,0x73,0xd1,0xc4,0xfd,0x3b,0xcc,0xfb,0x7f,0xab,0xe6,0x3e,0x5b,0xa5, + 0xad,0x04,0x23,0x9c,0x14,0x51,0x22,0xf0,0x29,0x79,0x71,0x7e,0xff,0x8c,0x0e,0xe2, + 0x0c,0xef,0xbc,0x72,0x75,0x6f,0x37,0xa1,0xec,0xd3,0x8e,0x62,0x8b,0x86,0x10,0xe8, + 0x08,0x77,0x11,0xbe,0x92,0x4f,0x24,0xc5,0x32,0x36,0x9d,0xcf,0xf3,0xa6,0xbb,0xac, + 0x5e,0x6c,0xa9,0x13,0x57,0x25,0xb5,0xe3,0xbd,0xa8,0x3a,0x01,0x05,0x59,0x2a,0x46, +]; + +Skip32.prototype.round16 = function(k, n){ + var g1, g2, g3, g4, g5, g6; + g1 = (n>>8) & 0xff; + g2 = (n>>0) & 0xff; + g3 = ftable[g2^this.key[(4*k+0)%10]] ^ g1; + g4 = ftable[g3^this.key[(4*k+1)%10]] ^ g2; + g5 = ftable[g4^this.key[(4*k+2)%10]] ^ g3; + g6 = ftable[g5^this.key[(4*k+3)%10]] ^ g4; + return (g5<<8)+g6; +} + +Skip32.prototype.core = function(n,k,d){ + var i, k, wl, wr; + wl = (((n>>24) & 0xff)<<8) + (((n>>16) & 0xff)<<0); + wr = (((n>>8) & 0xff)<<8) + (((n>>0) & 0xff)<<0); + for(i=0;i<24/2;i++){ + wr ^= this.round16(k, wl) ^ k; + k+=d; + wl ^= this.round16(k, wr) ^ k; + k+=d; + } + return ((wr << 16) | wl)>>>0; +} + +Skip32.prototype.encrypt = function(n){ + return this.core(n,0,1); +} + +Skip32.prototype.decrypt = function(n){ + return this.core(n,23,-1); +} + +module.exports.Skip32 = Skip32; diff --git a/src/index.js b/src/index.js index f83591f..91e3c50 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,10 @@ import Generator from './Generator'; +import {scramble,descramble} from './scrambler'; const arbitrary = { - Generator: Generator + Generator, + scramble, + descramble }; export default arbitrary; diff --git a/src/scrambler.js b/src/scrambler.js new file mode 100644 index 0000000..fe70890 --- /dev/null +++ b/src/scrambler.js @@ -0,0 +1,24 @@ +const Skip32 = require('./Skip32PureJS').Skip32; +const cypher = new Skip32(); + +/* + * Takes a unsigned 32 bit integer and returns an unsigned 32 bit integer + * with it's bits scrambled. + * + * Ideal for taking a series of incrementing numbers and creating a pseudo random version. + * Is reversible via calling descramble() on a scrambled number. + */ +export function scramble(number){ + return cypher.encrypt(number); +} + +/* + * Takes a scrambled unsigned 32 bit integer and returns the unscrambled unsigned 32 bit + * integer version. + * + * Ideal for taking a series of incrementing numbers and creating a pseudo random version. + * Use this to figure out the original number crated from calls to scramble() + */ +export function descramble(scrambledNumber){ + return cypher.decrypt(scrambledNumber); +} diff --git a/tests/index.js b/tests/index.js index a1bc415..a778795 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,5 +1,6 @@ import assert from 'assert'; import arbitrary from '../src/index'; +const Skip32 = require('../src/Skip32PureJS').Skip32; describe('arbitrary', function() { describe('module', function() { @@ -16,7 +17,7 @@ describe('arbitrary', function() { }); it('should generate random seed if no seed was provided', function () { - // Generate 10 random + // Test 10 randomly initialized generators // NOTE: It's technically possible but unlikely that a test run // could have collisions. If this ends up being an issue, then we // we could switch to calculating how many were different and consider @@ -93,19 +94,59 @@ describe('arbitrary', function() { } }); }); - }); - /*describe('#float()', function () { - it('should return an float', function () { - assert(isFloat(arbitrary.float()), "wasn't a float"); + + describe('scramble() / descramble()', function () { + it('should be versible', function () { + + // Test a bunch of sample numbers to see if they scramble and descramble correctly + const scramblingSamples = [ + // Low range numbers + 0, 1, 2, 4, 8, 16, 32, 64, + // High range numbers + Math.pow(2, 32) - 1, + Math.pow(2, 32) - 1 - 2, + Math.pow(2, 32) - 1 - 4, + Math.pow(2, 32) - 1 - 8, + Math.pow(2, 32) - 1 - 16, + Math.pow(2, 32) - 1 - 32, + Math.pow(2, 32) - 1 - 64, + // Also some randomly generated numbers, using the following console statement: + // for(let i = 0; i<10; i++){console.log( Math.floor( Math.random() * Math.pow(2, 32) ) ); } + 2599597252, + 4153347228, + 1043597882, + 1408751830, + 2053248205, + 2484325525, + 2803278095, + 1224986032, + 2895711202, + 3376187439 + ]; + + scramblingSamples.forEach( (sample) => { + const scrambled = arbitrary.scramble(sample); + assert( isValidUnsigned32BitInteger(scrambled), `scramble(${sample}) wasn't a valid u32` ); + + const unscrambled = arbitrary.descramble(scrambled); + assert( unscrambled === sample, `unscramble(scramble(${sample})) should have been reversible`); + }); + }); }); - });*/ + }); }); /** - * Check that a number is proper integer + * Check that a number is a valid unsigned 32 bit integer */ -function isInt(n){ - return Number(n) === n && n % 1 === 0; +function isValidUnsigned32BitInteger(n){ + // Can it cast to a number + return Number(n) === n && + // Make sure it has no decimal places + n % 1 === 0 && + // Make sure it's in the range [0, 2^32 - 1] + n >= 0 && + n < Math.pow(2,32); } /**