Skip to content

Commit

Permalink
slightly update api, update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
daywiss committed Feb 17, 2017
1 parent dfe3cac commit 9ad6e89
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 74 deletions.
95 changes: 65 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
# Provable
A Provably random engine which can be plugged into [random-js](https://www.npmjs.com/package/random-js).
A Provably fair random hash generator which can be plugged into [random-js](https://www.npmjs.com/package/random-js).

#Install
`npm install --save provable`

#About
This library attempts to implement the algorithm first outlined in the bitcoin talk formums:
This library attempts to implement the algorithm first outlined in the bitcoin talk forums:
[https://bitcointalk.org/index.php?topic=922898.0](https://bitcointalk.org/index.php?topic=922898.0).
The idea behind provably random is that a deteministic series of related but random hashes are generated before
any random outcomes are calculated. Once generated, each hash in the series is used to create a random outcome.
any random outcomes are calculated. Once generated, each hash in the series is used to create a random outcome.

The provable part comes in when a client wants to verify the integrity of the outcome. After every outcome
is produced the hash which produced it can also be made public. If one has access to the current hash and
the previous hash (from the previous outcome), then a SHA256 can be used on the current hash to generate the previous.
This should hold true for all hashes in the series as they are exposed. You cannot predict the next hash, but you
can verify the previous hash by hashing its predecessor. Any deviation from this verification result would mean that the hashes
were tampered with and not part of the pregenerated series. Hence you can "prove" the random outcome was fair.

Bear in mind since the series is pre-generated, this means eventually you will run out of hashes to generate random outcomes
and a new series of provably fair hashes must be created.

#Usage
This library can be used standalone to generate a series of random hashes generated by nodes crypto sha256 hasher. The engine can then
be plugged into random-js in order to give you more control over your random values. By default
the engine will only supply a 32-bit random integer based on the next hash in the series. The
state of the engine can be saved and resumed as needed.

When the engine runs out of hashes ( reaches end of series) an error is thrown. Its up to the user
to catch the error and determine how to proceed from that point. Typically you would switch to
or generate a new series and continue.


```js
var Provable = require('provable')

Expand All @@ -40,16 +49,17 @@ state of the engine can be saved and resumed as needed.
//internal state of the engine. Use this to save and resume your engine.
var state = engine.state()

//resuming will re-generate the entire hash chain from your seed and pick up where you
//left off with the index
//resuming will re-generate the entire hash chain
//from your seed and pick up where you left off with the index
var resumedEngine = Provable(state)

```

#Random-JS
[Random-js](https://www.npmjs.com/package/random-js) can give you more control over the values you can get. Keep in mind that random-js
only uses 32 bits of the hash, the rest is discarded. This provable engine defaults to using the 32 least
significant bits of the hash when generating the random integer.
[Random-js](https://www.npmjs.com/package/random-js) can give you more options over the types of
random values you can generate. Keep in mind that random-js only uses 32 bits of the hash,
the rest is discarded. This provable engine defaults to using the 32 least significant bits of
the hash when generating the random integer.

```js
var Random = require('random-js')
Expand All @@ -64,19 +74,43 @@ significant bits of the hash when generating the random integer.
random.real(min,max,inclusive)
//etc...

//keep in mind, the provable engine will throw when out of values, so you should wrap
//each call in try catch. When the engine throws, you should start a new serie When the engine throws, you should start a new series.

//keep in mind, the provable engine will throw when out of values,
//so you should wrap each call in try catch. When the engine throws,
//you should start a new series.

```
#Proving Fairness
When proving fairness to clients, you must expose each hash only after they are used to
generate the random outcome. Never expose your seed parameter. Generally you can provide
some third party site, like JSbin which takes a hash and generates the hash series
using SHA256. This series should match all your previously exposed hashes.
#Other Things to know
On construction, the engine will generate the entire hash series, the length of which is
determined by the "count" parameter. By default count is 1. It is suggested to change
this to something large, like 10000 or more. Hashes are generated syncronously so
there may be blocking for long periods if the count is very large. Hashes are also stored
in memory as 256-bit string, so memory usage can grow quickly.
Hashes are created in a deterministic way, so if you seed it with the same value, the hashes
will come out the same. Never expose your seed or all your outcomes can be predicted. If
you want random series every time, do not provide a seed on construction, a random UUIDv4 will
be generated for you.
All parameters passed into the engine at construction time represent the internal state of the
engine. As hashes are used, this state changes. State changes can be observed through
the onChange callback or polled through the engine.state() function. At any point
you can resume where you left off by constructing a new engine and passing in the last state.
When hashes run out, the call to get the next hash will fail and throw an error. Its up to you
to handle how to proceed when that happens. You can also peek at the next value to see if its
undefined, which means the next call will throw.
#API
##Construction
The provable engine has an internal state which you can pass in as the only argument to construction. By default
no parameters are needed, but its suggested you change at least the count argument to something large. This
will specify the number of calls you can make to get hashes before the engine errors.
Hashes are generated on instance construction and are generated sycnronously so long hash counts
may cause blocking. You can forgo seeding the engine, a random UUIDv4 will be generated as the seed.
Creating a hash series.
```js
var Provable = require('provable')
Expand Down Expand Up @@ -106,39 +140,40 @@ and produce random primitives. The integer is based on sampling the least signif
var integer = engine()
```
##nextHash()
This gets the next raw hash in the series and increments your hash index. Will throw an error if no more hashes are found.
##next()
This gets the next raw sha256 hash in the series and increments your hash index. Will throw an error if no more hashes are found.
If hashes run out, then generate a new engine with a new seed. Do not reuse old seeds as you will generate predictable
hashes.
```js
//the next raw sha256 hash in the series. Update hash index and throws if no more hashes found.
var hash = engine.nextHash()
var hash = engine.next()
```
##peekHash()
Peek at the next hash without changing engine state. Returns undefined if no hashes are found.
##peek(index)
Peek will return a hash without changing engine state. The index parameter is optional and will
default to the current engine state index, which is essentially the next hash. If index is provided
it will return to you the hash at that index. Returns undefined if you have reached the end of the
series or there is no hash at that index.
```js
//the next raw sha256 hash in the series, does not change state of engine.
var hash = engine.peekHash()
```
//will return undefined if no hash is found, or end of series will be reached
//with next call
var nextHash = engine.peek()

##getHash(index)
Get the hash at specified index. Does not update engine, allows you read access to your hash chain.
```js
//get hash at specific location in hash series. Series starts with hash 0 and ends with hash count-1.
//your engine state will show your current position with engine.state().index
var hash = engine.getHash(100)
//return hash at index 100
var specifiedHash = engine.peek(100)
```
##generate(count,seed)
Generate a raw hash series. This is used internally by the engine but is exposed in case its useful
to use directly. Consider it like a static function, does not reference the internal state of the
engine. The result is an array of hashes which should be used starting at series[0]. Its important
to use the hashes in the correct order, or they will be predictable.
```js
//generate 10000 hashes and returns an array of them with the seed value of "seed"
var series = engine.generate(10000,'seed')
var series = require('Provable').generate(10000,'seed')
```
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "provable",
"version": "0.1.0",
"description": "A Provably random engine which can be plugged into [random-js](https://www.npmjs.com/package/random-js).",
"keywords": ["random","bitcoin","provable","random generator","random engine","random-js","gambling","provably random","bustabit","hashes","hash","sha256"],
"description": "A Provably fair random hash generator which can be plugged into [random-js](https://www.npmjs.com/package/random-js).",
"keywords": ["random","bitcoin","provable","random generator","random engine","random-js","gambling","provably random","bustabit","hashes","hash","sha256","fair","provably fair"],
"main": "index.js",
"scripts": {
"test": "node test"
Expand Down
81 changes: 42 additions & 39 deletions provable.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ var seed = Random.generateEntropyArray()
var engine = Random.engines.mt19937().seedWithArray(seed)
var rand = new Random(engine)

function createSeed(){
return rand.uuid4()
}

function sha256(seed){
return crypto.createHash('sha256').update(seed).digest('hex');
}
//generate random hash series
function generate(count,seed){
seed = seed || createSeed()
count = count || 1
var result = Array(count)
for(var i = count-1; i >= 0; i--){
seed = sha256(seed)
result[i]= seed
}
return result
}

function Engine(options){
function defaults(options){
return lodash.defaults(lodash.clone(options),{
Expand All @@ -27,67 +46,51 @@ function Engine(options){
state.onChange(state)
}

function createSeed(){
return rand.uuid4()
}

function sha256(seed){
return crypto.createHash('sha256').update(seed).digest('hex');
}

//generate random hash series
function generate(count,seed){
seed = seed || createSeed()
count = count || 1
var result = Array(count)
for(var i = count-1; i >= 0; i--){
seed = sha256(seed)
result[i]= seed
}
return result
}

function nextHash(){
var hash = hashes[state.index++]
onChange(state)
assert(hash,'end of hash series')
if(state.clientSeed) hash = crypto.createHmac('sha256',hash).update(state.clientSeed).digest(hex)
return hash
}

function int32(hash){
return parseInt(hash.slice(-8),16)
}

function next(){
return int32(nextHash())
function engine(){
return int32(engine.next())
}

next.state = function(){
engine.state = function(){
return state
}
next.hashes = function(){

engine.hashes = function(){
return hashes
}
next.getHash = function(index){
return hashes[index]

engine.last = function(){
engine.peek(state.index-1)
}

next.peekHash = function(){
return hashes[state.index]
engine.peek = function(index){
if(index !== 0 && index == null) index = state.index
return hashes[index]
}

engine.next = function(){
var hash = hashes[state.index]
assert(hash,'end of hash series')
state.index++
onChange(state)
if(state.clientSeed) hash = crypto.createHmac('sha256',hash).update(state.clientSeed).digest(hex)
return hash
}

next.nextHash = nextHash
next.generate = generate

function init(){
state = defaults(options)
hashes = generate(state.count,state.seed)
onChange(state)
return next
return engine
}

return init()
}

Engine.generate = generate

module.exports = Engine
12 changes: 9 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test('provable',function(t){
})
t.test('state',function(t){
var state = engine.state()
t.equal(state.index-1,options.count)
t.equal(state.index,options.count)
t.end()
})
t.test('random',function(t){
Expand Down Expand Up @@ -78,11 +78,17 @@ test('provable',function(t){
engine = Engine(options)
lodash.times(100,engine)
var state = engine.state()
var next = engine.peekHash()
var next = engine.peek()

engine = Engine(state)
var hash = engine.nextHash()
var hash = engine.next()
t.equal(hash,next)
t.end()
})

t.test('generate',function(t){
var series = Engine.generate(10,'test')
t.equal(series.length,10)
t.end()
})
})

0 comments on commit 9ad6e89

Please sign in to comment.