up8-ticket

Securely generate UP8-compatible, @q-encoded master tickets.
Log | Files | Refs | README | LICENSE

commit 710384e2390ab59b834a9a4f1b56fe19066f2458
parent 7a9b48c55e356d643f549e3d7c375c17d3273c1e
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 25 Sep 2020 12:06:37 -0230

Merge branch 'drbg' into master

Diffstat:
MREADME.md | 39+++++++++++++++++++++++++++++++++------
Mpackage-lock.json | 13++++---------
Mpackage.json | 2++
Msrc/index.js | 39+++++++++++++++++++++++++++++++++++++++
Mtest/test.js | 25+++++++++++++++++++++++++
5 files changed, 103 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md @@ -7,6 +7,11 @@ Securely generate [UP8][up8p]-compatible, `@q`-encoded master tickets. Split and combine tickets via a k/n Shamir's Secret Sharing scheme. +If you plan on generating a master ticket for a galaxy wallet, for example, you +might want to use `gen_ticket_drbg(384)` to generate the ticket, and then +`shard(.., 5, 3)` to split it into five shares (any three of which can be used +to recover it). + ## Install Grab it from npm like so: @@ -48,7 +53,8 @@ Type ".help" for more information. ### gen\_ticket\_simple -Generate a 256-bit master ticket via a simple CSPRNG: +Generate a 256-bit master ticket via a simple CSPRNG (`crypto` or +`window.crypto`): ``` > up8.gen_ticket_simple(256) @@ -60,15 +66,15 @@ argument as a Buffer. It will simply be XOR'd with the random bytes produced internally: ``` -> up8.gen_ticket_simple(256, Buffer.from("a very very random string")) +> up8.gen_ticket_simple(256, Buffer.from("a very random string")) '~donryd-mallur-wanrex-fidrex-nidwyt-dildul-padryd-talfen-panneb-nocbep-norwep-mispel-ralryc-fiddun-tomsup-toltex' ``` ### gen\_ticket\_more -Do the same thing, but use [more-entropy][ment] to generate the ticket using -additional entropy. Note that it returns a Promise (and takes a little -longer): +Do the same thing, but also use [more-entropy][ment] to produce additional +entropy when generating the ticket. Note that it returns a Promise (and takes +a little longer): ``` > await up8.gen_ticket_more(256) @@ -82,6 +88,27 @@ You can similarly pass your own entropy in as an additional Buffer here: '~rivmer-ticnyd-mirfet-rolbyt-tarlus-ricrun-fitmec-losrul-barhep-misfet-pidfen-foshep-ronrem-natlyx-tarlet-sipdeb' ``` +### gen\_ticket\_drbg + +Do the same thing, but use a HMAC-DRBG function to combine the entropy produced +by the underlying CSPRNG and more-entropy. Like `gen_ticket_more`, it returns +a Promise, and takes longer. + +Note that you must use at least 192 bits of entropy for this method. + +``` +> await up8.gen_ticket_drbg(256) +'~morten-davnys-ronpes-hidtyd-pittev-donsug-fonpel-sornet-wacmeb-harbyl-monduc-linmur-racled-namdec-tildul-palmyn' +``` + +As with the other functions, you can pass your own entropy in as an additional +Buffer: + +``` +> let ticket = await up8.gen_ticket_drbg(384, Buffer.from('a personalization string')) +'~siller-hopryc-ripfyn-laglec-linpur-mogpun-poldux-bicmul-radnum-dapnup-monnub-dilwex-pacrym-samrup-ragryc-samdyt-timdys-hartul-lonrun-posmev-molrum-miclur-doznus-fasnut' +``` + ### shard Split a ticket into 'shards' using a k/n Shamir's Secret Sharing scheme. @@ -89,7 +116,7 @@ Specify the number of shards to create and the number of shards required to reassemble the original ticket, along with the ticket itself: ``` -> let ticket = await up8.gen_ticket_more(384) +> let ticket = await up8.gen_ticket_drbg(384) > let shards = up8.shard(ticket, 3, 2) > shards [ diff --git a/package-lock.json b/package-lock.json @@ -1,6 +1,6 @@ { "name": "up8-ticket", - "version": "0.2.0", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1723,7 +1723,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -1749,7 +1748,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -1826,8 +1824,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inline-source-map": { "version": "0.6.2", @@ -2329,14 +2326,12 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { "version": "3.0.4", diff --git a/package.json b/package.json @@ -26,6 +26,8 @@ "author": "~nidsut-tomdun", "license": "MIT", "dependencies": { + "hash.js": "^1.1.7", + "hmac-drbg": "^1.0.1", "lodash.chunk": "^4.2.0", "lodash.flatmap": "^4.5.0", "lodash.zipwith": "^4.2.0", diff --git a/src/index.js b/src/index.js @@ -1,5 +1,7 @@ const chunk = require('lodash.chunk') const flatMap = require('lodash.flatmap') +const hash = require('hash.js') +const DRBG = require('hmac-drbg') const more = require('more-entropy') const ob = require('urbit-ob') const secrets = require('secrets.js-grempe') @@ -93,6 +95,42 @@ const gen_ticket_more = (nbits, addl) => { } /* + * Generate a master ticket of the desired bitlength. + * + * Uses both 'crypto.rng' and 'more-entropy' to produce the required entropy + * and nonce for input to a HMAC-DRBG generator, respectively. + * + * A buffer provided as the second argument will be used as the DRBG + * personalisation string. + * + * @param {Number} nbits desired bitlength of ticket (minimum 192) + * @param {Buffer} addl an optional buffer of additional bytes + * @return {Promise<String>} a @q-encoded master ticket, wrapped in a Promise + */ +const gen_ticket_drbg = async (nbits, addl) => { + const nbytes = nbits / 8 + const entropy = crypto.rng(nbytes) + + const prng = new more.Generator() + const nonce = await new Promise((resolve, reject) => { + prng.generate(nbits, result => { + resolve(result.toString('hex')) + reject("entropy generation failed") + }) + }) + + const d = new DRBG({ + hash: hash.sha256, + entropy: entropy, + nonce: nonce, + pers: Buffer.isBuffer(addl) ? addl.toString('hex') : null + }) + + const bytes = d.generate(nbytes, 'hex') + return ob.hex2patq(bytes) +} + +/* * Shard a ticket via a k/n Shamir's Secret Sharing scheme. * * Provided with a ticket, a desired number of shards 'n', and threshold value @@ -136,6 +174,7 @@ const combine = shards => { module.exports = { gen_ticket_simple, gen_ticket_more, + gen_ticket_drbg, shard, combine diff --git a/test/test.js b/test/test.js @@ -70,6 +70,31 @@ describe('gen_ticket_more', () => { }) +describe('gen_ticket_drbg', () => { + + context("when no additional entropy provided", () => { + it('produces tickets of the correct bitlength', async () => { + let given_bits = 384 + let ticket = await mtg.gen_ticket_drbg(given_bits) + let hex = ob.patq2hex(ticket) + let expected_bits = hex.length * 4 + expect(expected_bits).to.equal(given_bits) + }) + }) + + context("when additional entropy provided", () => { + it('produces tickets of the correct bitlength', async () => { + let given_bits = 384 + let addl = Buffer.from("you'll really never predict this") + let ticket = await mtg.gen_ticket_drbg(given_bits, addl) + let hex = ob.patq2hex(ticket) + let expected_bits = hex.length * 4 + expect(expected_bits).to.equal(given_bits) + }) + }) + +}) + describe('shard', () => { it('throws on non-@q input', () => {