Skip to content

Commit

Permalink
Added scramble/descramble for more easily skip to different points in…
Browse files Browse the repository at this point in the history
… the random series of number the generator generates (#3)
  • Loading branch information
seflless authored Nov 28, 2016
1 parent 1eeab8c commit 4640077
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 10 deletions.
3 changes: 3 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Copyright (c) 2016 Francois Laberge <francoislaberge@gmail.com>

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
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 4 additions & 0 deletions RESEARCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
88 changes: 88 additions & 0 deletions src/Skip32PureJS.js
Original file line number Diff line number Diff line change
@@ -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 <bande@lut.fi>
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;
5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Generator from './Generator';
import {scramble,descramble} from './scrambler';

const arbitrary = {
Generator: Generator
Generator,
scramble,
descramble
};

export default arbitrary;
24 changes: 24 additions & 0 deletions src/scrambler.js
Original file line number Diff line number Diff line change
@@ -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);
}
59 changes: 50 additions & 9 deletions tests/index.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand Down

0 comments on commit 4640077

Please sign in to comment.