├── .gitignore ├── .travis.yml ├── tests ├── test.sh ├── Tests.elm └── elm-package.json ├── package.json ├── elm-package.json ├── example ├── elm-package.json └── CoinFlip.elm ├── README.md └── src ├── Native └── SecureRandom.js └── Random └── Secure.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | elm.js 3 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: haskell 2 | install: 3 | - npm install 4 | script: npm test 5 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | elm-package install -y 3 | console_version=$(node -pe 'JSON.parse(process.argv[1])["laszlopandy/elm-console"]' "$(cat elm-stuff/exact-dependencies.json)") 4 | 5 | elm make --yes --output elm-stuff/raw-test.js Tests.elm 6 | bash elm-stuff/packages/laszlopandy/elm-console/$console_version/elm-io.sh elm-stuff/raw-test.js elm-stuff/test.js 7 | node elm-stuff/test.js 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm-random-secure", 3 | "version": "0.0.0", 4 | "description": "Cryptographic random number generation for Elm.", 5 | "scripts": { 6 | "test": "cd tests && ./test.sh", 7 | "install": "elm package install -y" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/blacktaxi/elm-random-secure.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "elm": "^0.16.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.1", 3 | "summary": "Generate cryptographically random values", 4 | "repository": "https://github.com/blacktaxi/elm-random-secure.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Random.Secure" 11 | ], 12 | "native-modules": true, 13 | "dependencies": { 14 | "elm-lang/core": "4.0.0 <= v < 5.0.0" 15 | }, 16 | "elm-version": "0.17.0 <= v < 0.18.0" 17 | } -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Task 4 | import Console 5 | import ElmTest exposing (..) 6 | 7 | import Random.Secure exposing (..) 8 | 9 | hello : Test 10 | hello = 11 | suite "hello suite" <| 12 | [ test "hello test" <| 13 | assertEqual 5 5 14 | ] 15 | 16 | consoleTests : Console.IO () 17 | consoleTests = 18 | consoleRunner <| 19 | suite "All" <| 20 | [ hello 21 | ] 22 | 23 | port runner : Signal (Task.Task x ()) 24 | port runner = Console.run consoleTests 25 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "native-modules": true, 12 | "dependencies": { 13 | "elm-community/elm-test": "1.1.0 <= v < 2.0.0", 14 | "elm-lang/core": "3.0.0 <= v < 4.0.0" 15 | }, 16 | "elm-version": "0.17.0 <= v < 0.18.0" 17 | } -------------------------------------------------------------------------------- /example/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "native-modules": true, 12 | "dependencies": { 13 | "elm-lang/core": "3.0.0 <= v < 4.0.0", 14 | "evancz/elm-effects": "2.0.1 <= v < 3.0.0", 15 | "evancz/elm-html": "4.0.2 <= v < 5.0.0", 16 | "evancz/start-app": "2.0.2 <= v < 3.0.0" 17 | }, 18 | "elm-version": "0.16.0 <= v < 0.17.0" 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-random-secure [![Build Status](https://travis-ci.org/blacktaxi/elm-random-secure.svg)](https://travis-ci.org/blacktaxi/elm-random-secure) 2 | 3 | Cryptographic random number generation for Elm. 4 | 5 | A simple wrapper around the [`window.crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues) 6 | API to generate cryptographic random values in Elm. 7 | 8 | ## Example 9 | 10 | Random generation function return a `Task`. Depending on your use case, you might want to convert 11 | the task to an `Effects` or otherwise use it. 12 | 13 | Assuming you're using [start-app](https://github.com/evancz/start-app), in your `update` function: 14 | 15 | ```elm 16 | update : Action -> Model -> (Model, Effects Action) 17 | update action model = 18 | case action of 19 | Toss -> 20 | ( model 21 | , SecureRandom.bool -- generating a random Bool value here 22 | |> Task.toMaybe 23 | |> Task.map Catch 24 | |> Effects.task 25 | ) 26 | 27 | Catch coin -> 28 | ({ model | coin = coin }, Effects.none) 29 | ``` 30 | 31 | ### TODO 32 | 33 | - Tests 34 | - Invalid argument handling 35 | -------------------------------------------------------------------------------- /example/CoinFlip.elm: -------------------------------------------------------------------------------- 1 | module CoinFlip where 2 | 3 | import Effects exposing (Effects) 4 | import Signal exposing (Signal, Address) 5 | import Task exposing (Task) 6 | import StartApp 7 | import Html exposing (..) 8 | import Html.Events exposing (..) 9 | 10 | import Random.Secure 11 | 12 | type alias Model = { coin : Maybe Bool } 13 | 14 | type Action = Toss | Catch (Maybe Bool) 15 | 16 | initModel : Model 17 | initModel = { coin = Nothing } 18 | 19 | initAction : (Model, Effects Action) 20 | initAction = (initModel, Effects.none) 21 | 22 | update : Action -> Model -> (Model, Effects Action) 23 | update action model = 24 | case action of 25 | Toss -> 26 | ( model 27 | , Random.Secure.bool 28 | |> Task.toMaybe 29 | |> Task.map Catch 30 | |> Effects.task 31 | ) 32 | 33 | Catch coin -> 34 | ({ model | coin = coin }, Effects.none) 35 | 36 | view : Address Action -> Model -> Html 37 | view addr model = 38 | div [] <| 39 | case model.coin of 40 | Nothing -> 41 | [ button 42 | [ onClick addr Toss ] 43 | [ text "Toss a coin..." ] 44 | ] 45 | 46 | Just coin -> 47 | [ p 48 | [] 49 | [ text <| if coin then "Heads!" else "Tails!" ] 50 | , button 51 | [ onClick addr Toss] 52 | [ text "Toss again!" ] 53 | ] 54 | 55 | app : StartApp.App Model 56 | app = 57 | StartApp.start 58 | { init = initAction 59 | , update = update 60 | , view = view 61 | , inputs = [] 62 | } 63 | 64 | main : Signal Html 65 | main = app.html 66 | 67 | port tasks : Signal (Task.Task Effects.Never ()) 68 | port tasks = app.tasks 69 | -------------------------------------------------------------------------------- /src/Native/SecureRandom.js: -------------------------------------------------------------------------------- 1 | Elm.Native.SecureRandom = {}; // eslint-disable-line no-undef 2 | Elm.Native.SecureRandom.make = function(localRuntime) { // eslint-disable-line no-undef 3 | localRuntime.Native = localRuntime.Native || {}; 4 | localRuntime.Native.SecureRandom = localRuntime.Native.SecureRandom || {}; 5 | if (localRuntime.Native.SecureRandom.values) { 6 | return localRuntime.Native.SecureRandom.values; 7 | } 8 | 9 | var Task = Elm.Native.Task.make(localRuntime); // eslint-disable-line no-undef 10 | 11 | if (typeof window === 'object' && typeof window.crypto === 'object' 12 | && typeof window.crypto.getRandomValues === 'function') { 13 | var crypto = window.crypto; 14 | 15 | function runAsTask(action) { 16 | return Task.asyncFunction(function (callback) { 17 | var result, errorName = null, errorMessage = null; 18 | 19 | try { result = action(); } 20 | catch (err) { 21 | if (typeof err === 'object') { 22 | errorName = err.name || ""; 23 | errorMessage = err.message || err.toString(); 24 | } else { 25 | errorName = typeof err; 26 | errorMessage = err.toString(); 27 | } 28 | } 29 | 30 | if (errorName !== null) { 31 | callback(Task.fail({ ctor: 'Exception', _0: errorName, _1: errorMessage })); 32 | } else { 33 | callback(Task.succeed(result)); 34 | } 35 | }); 36 | } 37 | 38 | return localRuntime.Native.SecureRandom.values = { 39 | getRandomInts: function (count) { 40 | return runAsTask(function () { 41 | return crypto.getRandomValues(new Uint32Array(count)); 42 | }); 43 | }, 44 | 45 | getRandomInt: function () { 46 | return runAsTask(function () { 47 | return crypto.getRandomValues(new Uint32Array(1))[0]; 48 | }); 49 | } 50 | }; 51 | } else { 52 | var fail = function () { 53 | return Task.asyncFunction(function (callback) { 54 | callback(Task.fail({ ctor: 'NoGetRandomValues' })); 55 | }); 56 | }; 57 | 58 | return localRuntime.Native.SecureRandom.values = { 59 | getRandomInts: fail, 60 | getRandomInt: fail 61 | }; 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/Random/Secure.elm: -------------------------------------------------------------------------------- 1 | module Random.Secure exposing (int, ints, bool, bools, float, floats) 2 | 3 | {-| A library for generating cryptographically random values. 4 | 5 | Contrary to the [`Random`](#Random) module, which provides purely functional, 6 | and therefore repeatable random generators, this module allows you to 7 | generate cryptographically random values that can be used in applications 8 | where security is required. 9 | 10 | All generation functions return a [`Task`](#Task), as the 11 | generation algorithm is dependent on the browser's global state 12 | (the [`getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues) function]) 13 | and is not referentially transparent. 14 | 15 | *Note:* As this library depends on the 16 | [`window.crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues) 17 | JavaScript API, it therefore will share all the randomness qualities with the underlying 18 | implementation. 19 | 20 | # Primitive random values 21 | @docs int, bool, float 22 | 23 | # Lists of random values 24 | @docs ints, bools, floats 25 | 26 | -} 27 | 28 | import Native.SecureRandom 29 | import Task exposing (Task) 30 | 31 | type Error 32 | = NoGetRandomValues 33 | | Exception String String 34 | 35 | {-| Generate a random 32-bit integer in a given range. 36 | 37 | int 0 10 -- an integer between zero and ten 38 | int -5 5 -- an integer between -5 and 5 39 | int 0 (2 ^ 32 - 1) -- an integer in the widest range feasible 40 | 41 | This function can *not* produce sufficiently random values if `abs (to - from)` is 42 | greater than the largest unsigned 32-bit integer (usually `4294967295`). 43 | 44 | Moreover, the quality of the produced output is dependent on 45 | `window.crypto.getRandomValues` – which is what this function uses under the hood. 46 | -} 47 | int : Int -> Int -> Task Error Int 48 | int from to = 49 | Native.SecureRandom.getRandomInt () 50 | |> Task.map (compressInt from to) 51 | 52 | {-| Generate a list of random 32-bit integers in a given range. 53 | 54 | ints 3 133 7 -- a list of 7 integers between 3 and 133 55 | -} 56 | ints : Int -> Int -> Int -> Task Error (List Int) 57 | ints from to n = 58 | -- @TODO how do we arg check here? do we throw errors in JS? Task.fail? etc? 59 | Native.SecureRandom.getRandomInts (min 0 n) 60 | |> Task.map (List.map (compressInt from to)) 61 | 62 | {-| Generate a random boolean value. 63 | 64 | type Flip = Heads | Tails 65 | 66 | coinFlip : Task x Flip 67 | coinFlip = map (\x -> if x then Heads else Tails) bool 68 | -} 69 | bool : Task Error Bool 70 | bool = 71 | Native.SecureRandom.getRandomInt () 72 | |> Task.map (\x -> x % 2 == 0) 73 | 74 | {-| Generate a list of random boolean values. 75 | -} 76 | bools : Int -> Task Error (List Bool) 77 | bools n = 78 | Native.SecureRandom.getRandomInts (min 0 n) 79 | |> Task.map (List.map (\x -> x % 2 == 0)) 80 | 81 | {-| Generate a random floating point number in a given range. 82 | 83 | float 3.1 33.7 -- a floating point number between 3.1 and 33.7 84 | -} 85 | float : Float -> Float -> Task Error Float 86 | float from to = 87 | Native.SecureRandom.getRandomInt () 88 | |> Task.map (compressFloat from to) 89 | 90 | {-| Generate a list of random floating point numbers in a given range. 91 | 92 | floats 3.1 3.3 7 -- a list of 7 floating point numbers between 3.1 and 3.3 93 | -} 94 | floats : Float -> Float -> Int -> Task Error (List Float) 95 | floats from to n = 96 | Native.SecureRandom.getRandomInts (min 0 n) 97 | |> Task.map (List.map (compressFloat from to)) 98 | 99 | compressInt : Int -> Int -> Int -> Int 100 | compressInt from to x = x % (to - from + 1) + from 101 | 102 | compressFloat : Float -> Float -> Int -> Float 103 | compressFloat from to x = from + (to - from) * (toFloat x / toFloat maxUint32) 104 | 105 | maxUint32 : Int 106 | maxUint32 = 2 ^ 32 - 1 107 | --------------------------------------------------------------------------------