├── .travis.yml ├── LICENSE.md ├── README.md ├── index.js ├── package.json └── test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | language: node_js 4 | node_js: 5 | - node 6 | deploy: 7 | provider: npm 8 | email: github@tixz.dk 9 | api_key: 10 | secure: JAZpFufERpbS0Ph0Oh6vDqKE34TSnB/93E+Wad6iakamEGCO+4wocxMpEkdFqwoFpQH7f08UV/qmKW74Rgx8b8HkfRIIokHhLKGPXwx8TzsAFbfoedKu/UhZKPiAlRGr9s/tCR3Y8kRlXgsifIdh7oeqiiRvDfmAQLTEa4wCWmyrJxXUqwxymp1RdqzyG9U26fCg1vMNpZonu9tLGGXOXZhn4PObB3Yvx4z5S9bHC2JToRugCqjRYlpZiZ0JdC1PZiGHkh+JL/aMEgT0XHb6ndro0wk1tvVld5dMASIvUid1Omb+T36wMNAbqio+jWt4FaZ/7j4ERzfpkhvYjtruR8drttzMatacGKWZ+Y1twR9oPSb26ZTl20xB/puAsLlbUT60MAeyzfH2plxsxiiUchWplgXwY059P+CBq2gbYsUIDUFSFo4fkSFY05s+tnCqPJ8527aK77RlHl/VJW2Lk2W6ALoX1HnsM2GTCTR6Pnr+9LC2isYP1qNJnaVJmbhe/SoDF1DH4ZpfZIgpFTmPeZPKS4BNweqFmcMOzz427KkfxNkZc0N/38JS+cowMFg9BtoMuo7WQPXyDtom6QIv/S5zQfRuWfjTnaaeh6qZLI7DuvxgqeKGqdblXJAadww/ZB8YKXZcPJnGJZydjlh72sxGVI9lIGg65dRTT01ecCk= 11 | on: 12 | tags: true 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Emil Bay 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `sodium-uuid` 2 | [![Build Status](https://travis-ci.org/emilbayes/sodium-uuid.svg?branch=master)](https://travis-ci.org/emilbayes/sodium-uuid) 3 | 4 | > Generate v4 UUIDs using libsodium's RNG 5 | 6 | ## Usage 7 | 8 | ```js 9 | var uuid = require('sodium-uuid') 10 | 11 | uuid() // => Buffer 12 | uuid(Buffer.allocUnsafe(uuid.BYTES)) // => allocUnsafe'ed 16 byte Buffer 13 | ``` 14 | 15 | ## API 16 | 17 | ### `var buf = uuid([buf])` 18 | Optional argument `buf` must be a `Buffer` of at least 128 bits (16 bytes). 19 | This module will fill the first 16 bytes with random bits but set the 20 | appropriate bits to be recognised as a UUID v4. This leaves 122 bits of entropy. 21 | This method does not insert dashes in the formatting, but this can be done with 22 | the `uuid.stringify` method. 23 | 24 | ### `var str = uuid.stringify(buf)` 25 | Convert `buf` to string representation of UUID eg. 26 | `4a181507-72e2-45c7-a512-9d9601425b2d`. Will only read the first 16 bytes 27 | of `buf`. 28 | 29 | ### `var buf = uuid.parse(str, [buf])` 30 | Convert `str` to `Buffer` representation of UUID, written to optional argument 31 | `buf`, which must be `Buffer` of at least length `uuid.BYTES`. Will be allocated 32 | if not given. Will fail if `str` is not a valid UUIDv4 33 | 34 | ### `var bool = uuid.isUUID(obj)` 35 | Check that `obj` is a valid UUIDv4. `obj` can be either a string or `Buffer` 36 | 37 | ### `uuid.BYTES` 38 | Constant defining the number of bytes `buf` must be able to contain. 39 | 40 | ## Install 41 | 42 | ```sh 43 | npm install sodium-uuid 44 | ``` 45 | 46 | ## License 47 | 48 | [ISC](LICENSE.md) 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var sodium = require('sodium-native') 3 | var assert = require('assert') 4 | 5 | var BYTES = 16 6 | 7 | module.exports = create 8 | function create (buf) { 9 | assert(buf == null ? true : Buffer.isBuffer(buf), 'buf must be Buffer') 10 | assert(buf == null ? true : buf.byteLength >= BYTES, 'buf must be at least BYTES (' + BYTES + ') bytes') 11 | 12 | if (buf == null) { 13 | buf = Buffer.allocUnsafe(BYTES) 14 | } 15 | 16 | sodium.randombytes_buf(buf.slice(0, BYTES)) 17 | 18 | // Mask then set bits 19 | // https://tools.ietf.org/html/rfc4122#section-4.1.2 20 | 21 | // time_hi_and_version (MSB half) 22 | // IETF variant 23 | buf[6] = (buf[6] & 0b00001111) | 0b01000000 24 | 25 | // clock_seq_hi_and_reserved 26 | // version 27 | buf[8] = (buf[8] & 0b00111111) | 0b10000000 28 | 29 | return buf 30 | } 31 | 32 | var REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[ab89][a-f0-9]{3}-[a-f0-9]{12}$/i 33 | create.isUUID = function (uuid) { 34 | if (typeof uuid === 'string') return uuid.length === 36 && REGEX.test(uuid) 35 | 36 | assert(Buffer.isBuffer(uuid), 'uuid must be string or Buffer') 37 | 38 | return uuid.byteLength >= BYTES && 39 | (uuid[6] & 0b11110000) === 0b01000000 && 40 | (uuid[8] & 0b11000000) === 0b10000000 41 | } 42 | 43 | create.stringify = function (buf) { 44 | assert(Buffer.isBuffer(buf), 'buf must be Buffer') 45 | assert(buf.byteLength >= BYTES, 'buf must be at least BYTES (' + BYTES + ') bytes') 46 | assert(create.isUUID(buf), 'Could not parse buf (' + buf + '): Invalid UUIDv4') 47 | 48 | return [ 49 | buf.slice(0, 4), 50 | buf.slice(4, 6), 51 | buf.slice(6, 8), 52 | buf.slice(8, 10), 53 | buf.slice(10, 16) 54 | ].map(function (subbuf) { 55 | return subbuf.toString('hex') 56 | }).join('-') 57 | } 58 | 59 | create.parse = function (str, buf) { 60 | if (buf == null) buf = Buffer.allocUnsafe(BYTES) 61 | buf.write(str.replace(/-/g, ''), 0, BYTES, 'hex') 62 | 63 | assert(create.isUUID(buf), 'Could not parse str (' + str + '): Invalid UUIDv4') 64 | 65 | return buf 66 | } 67 | 68 | create.BYTES = BYTES 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sodium-uuid", 3 | "version": "2.0.0", 4 | "description": "Generate v4 UUIDs using libsodium's RNG", 5 | "main": "index.js", 6 | "dependencies": { 7 | "sodium-native": "^3.2.0" 8 | }, 9 | "devDependencies": { 10 | "is-uuid": "^1.0.2", 11 | "tape": "^4.6.3" 12 | }, 13 | "scripts": { 14 | "test": "tape test.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/emilbayes/sodium-uuid.git" 19 | }, 20 | "keywords": [ 21 | "rng", 22 | "uuid", 23 | "uuidv4", 24 | "uuid4", 25 | "v4", 26 | "libsodium", 27 | "sodium" 28 | ], 29 | "author": "Emil Bay ", 30 | "license": "ISC", 31 | "bugs": { 32 | "url": "https://github.com/emilbayes/sodium-uuid/issues" 33 | }, 34 | "homepage": "https://github.com/emilbayes/sodium-uuid#readme" 35 | } 36 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var uuid = require('.') 3 | var isUuid = require('is-uuid') 4 | 5 | test('constants', function (assert) { 6 | assert.same(typeof uuid.BYTES, 'number') 7 | assert.ok(uuid.BYTES > 0) 8 | 9 | assert.end() 10 | }) 11 | 12 | test('returns valid uuid', function (assert) { 13 | var uuid1 = uuid() 14 | var uuid2 = uuid(Buffer.alloc(16)) 15 | var uuid3 = uuid(Buffer.alloc(32)) 16 | var uuid4 = uuid(Buffer.alloc(uuid.BYTES)) 17 | 18 | assert.notOk(uuid1.equals(uuid2), 'not same uuid') 19 | assert.notOk(uuid1.equals(uuid3.slice(0, 16)), 'not same uuid') 20 | assert.notOk(uuid1.equals(uuid4), 'not same uuid') 21 | assert.notOk(uuid2.equals(uuid3.slice(0, 16)), 'not same uuid') 22 | assert.notOk(uuid2.equals(uuid4), 'not same uuid') 23 | assert.notOk(uuid3.slice(0, 16).equals(uuid4), 'not same uuid') 24 | 25 | assert.ok(uuid3.slice(16).equals(Buffer.alloc(16)), 'upper 16 bytes still clear') 26 | 27 | assert.ok(isUuid.v4(uuid.stringify(uuid1)), 'valid uuid v4') 28 | assert.ok(isUuid.v4(uuid.stringify(uuid2)), 'valid uuid v4') 29 | assert.ok(isUuid.v4(uuid.stringify(uuid3)), 'valid uuid v4') 30 | assert.ok(isUuid.v4(uuid.stringify(uuid4)), 'valid uuid v4') 31 | 32 | assert.end() 33 | }) 34 | 35 | test('degenerate cases', function (assert) { 36 | assert.throws(function () { uuid(Buffer.alloc(0)) }) 37 | assert.throws(function () { uuid(Buffer.alloc(1)) }) 38 | assert.throws(function () { uuid(Buffer.alloc(8)) }) 39 | assert.throws(function () { uuid(Buffer.alloc(15)) }) 40 | assert.throws(function () { uuid(Buffer.alloc(uuid.BYTES - 1)) }) 41 | 42 | assert.end() 43 | }) 44 | 45 | test('parse/stringify', function (assert) { 46 | for (var i = 0; i < 1e5; i++) { 47 | var id = uuid() 48 | var str = uuid.stringify(id) 49 | var cid = uuid.parse(str) 50 | 51 | if (!id.equals(cid)) assert.fail('id != cid') 52 | } 53 | 54 | assert.end() 55 | }) 56 | 57 | test('isUUID', function (assert) { 58 | for (var i = 0; i < 1e5; i++) { 59 | var id = uuid() 60 | var str = uuid.stringify(id) 61 | 62 | if (!uuid.isUUID(id)) assert.fail('id not uuid') 63 | if (!uuid.isUUID(str)) assert.fail('str not uuid') 64 | } 65 | 66 | assert.end() 67 | }) 68 | --------------------------------------------------------------------------------