bip39

BIP39 mnemonic codes in Haskell (docs.ppad.tech/bip39).
git clone git://git.ppad.tech/bip39.git
Log | Files | Refs | README | LICENSE

commit bc25954dd7207249d83438d092619d4c0e28514a
Author: Jared Tobin <jared@jtobin.io>
Date:   Wed, 26 Feb 2025 20:07:31 +0400

lib: init

Diffstat:
A.gitignore | 1+
ACHANGELOG | 0
ALICENSE | 20++++++++++++++++++++
Abench/Main.hs | 4++++
Aflake.lock | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflake.nix | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/Crypto/KDF/BIP39.hs | 2115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Appad-bip39.cabal | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/Main.hs | 4++++
9 files changed, 2554 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +dist-newstyle/ diff --git a/CHANGELOG b/CHANGELOG diff --git a/LICENSE b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2025 Jared Tobin + +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 +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bench/Main.hs b/bench/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = pure () diff --git a/flake.lock b/flake.lock @@ -0,0 +1,257 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1725910328, + "narHash": "sha256-n9pCtzGZ0httmTwMuEbi5E78UQ4ZbQMr1pzi5N0LAG8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5775c2583f1801df7b790bf7f7d710a19bac66f4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "ppad-base16": { + "inputs": { + "flake-utils": [ + "ppad-base16", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-base16", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-nixpkgs" + ] + }, + "locked": { + "lastModified": 1739979569, + "narHash": "sha256-omEcmgzRlzIE5Vdty0/SskEcR2f7OtcHzGFE4i1dI60=", + "ref": "master", + "rev": "4439e0efafbb5185bd7d9bfb352a17c2a31b96b4", + "revCount": 15, + "type": "git", + "url": "git://git.ppad.tech/base16.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/base16.git" + } + }, + "ppad-base16_2": { + "inputs": { + "flake-utils": [ + "ppad-pbkdf", + "ppad-base16", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-pbkdf", + "ppad-base16", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-pbkdf", + "ppad-nixpkgs" + ] + }, + "locked": { + "lastModified": 1739979569, + "narHash": "sha256-omEcmgzRlzIE5Vdty0/SskEcR2f7OtcHzGFE4i1dI60=", + "ref": "master", + "rev": "4439e0efafbb5185bd7d9bfb352a17c2a31b96b4", + "revCount": 15, + "type": "git", + "url": "git://git.ppad.tech/base16.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/base16.git" + } + }, + "ppad-nixpkgs": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1737297101, + "narHash": "sha256-EnXnq+JLflbWt+DvaGGnY2gfAqsGNOm5vPgHh3hkfwQ=", + "ref": "master", + "rev": "f29823875250bc99b3891f7373535ccde9a29a44", + "revCount": 1, + "type": "git", + "url": "git://git.ppad.tech/nixpkgs.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/nixpkgs.git" + } + }, + "ppad-pbkdf": { + "inputs": { + "flake-utils": [ + "ppad-pbkdf", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-pbkdf", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-base16": "ppad-base16_2", + "ppad-nixpkgs": [ + "ppad-nixpkgs" + ], + "ppad-sha256": [ + "ppad-sha256" + ], + "ppad-sha512": [ + "ppad-sha512" + ] + }, + "locked": { + "lastModified": 1740456623, + "narHash": "sha256-s29IVCEZMOhlq56Z4DAq+2UX8yYn/f+n3mIQMTGbuaQ=", + "ref": "master", + "rev": "d249a794ca47cb4e3abe494dcde80767d84b8632", + "revCount": 12, + "type": "git", + "url": "git://git.ppad.tech/pbkdf.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/pbkdf.git" + } + }, + "ppad-sha256": { + "inputs": { + "flake-utils": [ + "ppad-sha256", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-sha256", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-nixpkgs" + ] + }, + "locked": { + "lastModified": 1739979751, + "narHash": "sha256-FSU4s+TtpqdrWn9DYmFprSnlD6pEcVQmsPX+m8oadvo=", + "ref": "master", + "rev": "a0e34487f0c025fac1addf72b96498e6084e4e8f", + "revCount": 93, + "type": "git", + "url": "git://git.ppad.tech/sha256.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/sha256.git" + } + }, + "ppad-sha512": { + "inputs": { + "flake-utils": [ + "ppad-sha512", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-sha512", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-nixpkgs" + ] + }, + "locked": { + "lastModified": 1738754198, + "narHash": "sha256-6XuhiWKKAfplrLM7kKBrmkbaJ7hKzqpzV6fnHKqO2xA=", + "ref": "master", + "rev": "2b2fb3318127985b8eb8f361d6d8cbb86f08b2ed", + "revCount": 25, + "type": "git", + "url": "git://git.ppad.tech/sha512.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/sha512.git" + } + }, + "root": { + "inputs": { + "flake-utils": [ + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-base16": "ppad-base16", + "ppad-nixpkgs": "ppad-nixpkgs", + "ppad-pbkdf": "ppad-pbkdf", + "ppad-sha256": "ppad-sha256", + "ppad-sha512": "ppad-sha512" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix @@ -0,0 +1,84 @@ +{ + description = "Pure Haskell BIP39 hierarchical deterministic wallets."; + + inputs = { + ppad-nixpkgs = { + type = "git"; + url = "git://git.ppad.tech/nixpkgs.git"; + ref = "master"; + }; + ppad-sha256 = { + type = "git"; + url = "git://git.ppad.tech/sha256.git"; + ref = "master"; + inputs.ppad-nixpkgs.follows = "ppad-nixpkgs"; + }; + ppad-sha512 = { + type = "git"; + url = "git://git.ppad.tech/sha512.git"; + ref = "master"; + inputs.ppad-nixpkgs.follows = "ppad-nixpkgs"; + }; + ppad-pbkdf = { + type = "git"; + url = "git://git.ppad.tech/pbkdf.git"; + ref = "master"; + inputs.ppad-nixpkgs.follows = "ppad-nixpkgs"; + inputs.ppad-sha256.follows = "ppad-sha256"; + inputs.ppad-sha512.follows = "ppad-sha512"; + }; + flake-utils.follows = "ppad-nixpkgs/flake-utils"; + nixpkgs.follows = "ppad-nixpkgs/nixpkgs"; + }; + + outputs = { self, nixpkgs, flake-utils, ppad-nixpkgs + , ppad-sha256, ppad-sha512 + , ppad-pbkdf + }: + flake-utils.lib.eachDefaultSystem (system: + let + lib = "ppad-bip39"; + + pkgs = import nixpkgs { inherit system; }; + hlib = pkgs.haskell.lib; + + hpkgs = pkgs.haskell.packages.ghc981.extend (new: old: { + ${lib} = old.callCabal2nixWithOptions lib ./. "--enable-profiling" {}; + ppad-sha256 = ppad-sha256.packages.${system}.default; + ppad-sha512 = ppad-sha512.packages.${system}.default; + ppad-pbkdf = ppad-pbkdf.packages.${system}.default; + }); + + cc = pkgs.stdenv.cc; + ghc = hpkgs.ghc; + cabal = hpkgs.cabal-install; + in + { + packages.default = hpkgs.${lib}; + + devShells.default = hpkgs.shellFor { + packages = p: [ + (hlib.doBenchmark p.${lib}) + ]; + + buildInputs = [ + cabal + cc + ]; + + inputsFrom = builtins.attrValues self.packages.${system}; + + doBenchmark = true; + + shellHook = '' + PS1="[${lib}] \w$ " + echo "entering ${system} shell, using" + echo "cc: $(${cc}/bin/cc --version)" + echo "ghc: $(${ghc}/bin/ghc --version)" + echo "cabal: $(${cabal}/bin/cabal --version)" + ''; + }; + } + ); +} + diff --git a/lib/Crypto/KDF/BIP39.hs b/lib/Crypto/KDF/BIP39.hs @@ -0,0 +1,2115 @@ +{-# LANGUAGE BinaryLiterals #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} + +module Crypto.KDF.BIP39 where + +import qualified Crypto.KDF.PBKDF as PBKDF +import qualified Crypto.Hash.SHA256 as SHA256 +import qualified Crypto.Hash.SHA512 as SHA512 +import Data.Bits ((.&.), (.|.), (.>>.), (.<<.)) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Internal as BI +import qualified Data.ByteString.Unsafe as BU +import qualified Data.Primitive.Array as PA +import Data.Word (Word64) +import qualified Data.List as L +import Prelude hiding (words) + +newtype Mnemonic = Mnemonic BS.ByteString + deriving Eq + +instance Show Mnemonic where + show (Mnemonic bs) = show bs + +fi :: (Integral a, Num b) => a -> b +fi = fromIntegral +{-# INLINE fi #-} + +type Acc = (BS.ByteString, Word64, Int) + +words :: BS.ByteString -> [BS.ByteString] +words bs = L.unfoldr coalg (bs, 0, 0) where + mask = 0b0111_1111_1111 + coalg :: Acc -> Maybe (BS.ByteString, Acc) + coalg (etc, acc, len) + | len > 10 = + let w11 = fi ((acc .>>. (len - 11)) .&. mask) + nacc = acc .&. ((1 .<<. (len - 11)) - 1) + nlen = len - 11 + word = PA.indexArray english w11 + in Just (word, (etc, nacc, nlen)) + | not (BS.null etc) = + let next = BU.unsafeHead etc + rest = BU.unsafeTail etc + nacc = (acc .<<. 8) .|. fi next + nlen = len + 8 + in coalg (rest, nacc, nlen) + | otherwise = + Nothing + +mnemonic :: BS.ByteString -> Mnemonic +mnemonic entropy@(BI.PS _ _ l) + | l < 16 = error "ppad-bip39 (mnemonic): invalid entropy length" + | l > 32 = error "ppad-bip39 (mnemonic): invalid entropy length" + | otherwise = + let kek = BS.take (l `quot` 4) (SHA256.hash entropy) + cat = entropy <> kek + in Mnemonic (BS.intercalate " " (words cat)) + + +seed :: BS.ByteString -> BS.ByteString -> BS.ByteString +seed mnem pass = PBKDF.derive SHA512.hmac mnem salt 2048 64 where + salt = "mnemonic" <> pass + +english :: PA.Array BS.ByteString +english = PA.arrayFromList [ + "abandon" + , "ability" + , "able" + , "about" + , "above" + , "absent" + , "absorb" + , "abstract" + , "absurd" + , "abuse" + , "access" + , "accident" + , "account" + , "accuse" + , "achieve" + , "acid" + , "acoustic" + , "acquire" + , "across" + , "act" + , "action" + , "actor" + , "actress" + , "actual" + , "adapt" + , "add" + , "addict" + , "address" + , "adjust" + , "admit" + , "adult" + , "advance" + , "advice" + , "aerobic" + , "affair" + , "afford" + , "afraid" + , "again" + , "age" + , "agent" + , "agree" + , "ahead" + , "aim" + , "air" + , "airport" + , "aisle" + , "alarm" + , "album" + , "alcohol" + , "alert" + , "alien" + , "all" + , "alley" + , "allow" + , "almost" + , "alone" + , "alpha" + , "already" + , "also" + , "alter" + , "always" + , "amateur" + , "amazing" + , "among" + , "amount" + , "amused" + , "analyst" + , "anchor" + , "ancient" + , "anger" + , "angle" + , "angry" + , "animal" + , "ankle" + , "announce" + , "annual" + , "another" + , "answer" + , "antenna" + , "antique" + , "anxiety" + , "any" + , "apart" + , "apology" + , "appear" + , "apple" + , "approve" + , "april" + , "arch" + , "arctic" + , "area" + , "arena" + , "argue" + , "arm" + , "armed" + , "armor" + , "army" + , "around" + , "arrange" + , "arrest" + , "arrive" + , "arrow" + , "art" + , "artefact" + , "artist" + , "artwork" + , "ask" + , "aspect" + , "assault" + , "asset" + , "assist" + , "assume" + , "asthma" + , "athlete" + , "atom" + , "attack" + , "attend" + , "attitude" + , "attract" + , "auction" + , "audit" + , "august" + , "aunt" + , "author" + , "auto" + , "autumn" + , "average" + , "avocado" + , "avoid" + , "awake" + , "aware" + , "away" + , "awesome" + , "awful" + , "awkward" + , "axis" + , "baby" + , "bachelor" + , "bacon" + , "badge" + , "bag" + , "balance" + , "balcony" + , "ball" + , "bamboo" + , "banana" + , "banner" + , "bar" + , "barely" + , "bargain" + , "barrel" + , "base" + , "basic" + , "basket" + , "battle" + , "beach" + , "bean" + , "beauty" + , "because" + , "become" + , "beef" + , "before" + , "begin" + , "behave" + , "behind" + , "believe" + , "below" + , "belt" + , "bench" + , "benefit" + , "best" + , "betray" + , "better" + , "between" + , "beyond" + , "bicycle" + , "bid" + , "bike" + , "bind" + , "biology" + , "bird" + , "birth" + , "bitter" + , "black" + , "blade" + , "blame" + , "blanket" + , "blast" + , "bleak" + , "bless" + , "blind" + , "blood" + , "blossom" + , "blouse" + , "blue" + , "blur" + , "blush" + , "board" + , "boat" + , "body" + , "boil" + , "bomb" + , "bone" + , "bonus" + , "book" + , "boost" + , "border" + , "boring" + , "borrow" + , "boss" + , "bottom" + , "bounce" + , "box" + , "boy" + , "bracket" + , "brain" + , "brand" + , "brass" + , "brave" + , "bread" + , "breeze" + , "brick" + , "bridge" + , "brief" + , "bright" + , "bring" + , "brisk" + , "broccoli" + , "broken" + , "bronze" + , "broom" + , "brother" + , "brown" + , "brush" + , "bubble" + , "buddy" + , "budget" + , "buffalo" + , "build" + , "bulb" + , "bulk" + , "bullet" + , "bundle" + , "bunker" + , "burden" + , "burger" + , "burst" + , "bus" + , "business" + , "busy" + , "butter" + , "buyer" + , "buzz" + , "cabbage" + , "cabin" + , "cable" + , "cactus" + , "cage" + , "cake" + , "call" + , "calm" + , "camera" + , "camp" + , "can" + , "canal" + , "cancel" + , "candy" + , "cannon" + , "canoe" + , "canvas" + , "canyon" + , "capable" + , "capital" + , "captain" + , "car" + , "carbon" + , "card" + , "cargo" + , "carpet" + , "carry" + , "cart" + , "case" + , "cash" + , "casino" + , "castle" + , "casual" + , "cat" + , "catalog" + , "catch" + , "category" + , "cattle" + , "caught" + , "cause" + , "caution" + , "cave" + , "ceiling" + , "celery" + , "cement" + , "census" + , "century" + , "cereal" + , "certain" + , "chair" + , "chalk" + , "champion" + , "change" + , "chaos" + , "chapter" + , "charge" + , "chase" + , "chat" + , "cheap" + , "check" + , "cheese" + , "chef" + , "cherry" + , "chest" + , "chicken" + , "chief" + , "child" + , "chimney" + , "choice" + , "choose" + , "chronic" + , "chuckle" + , "chunk" + , "churn" + , "cigar" + , "cinnamon" + , "circle" + , "citizen" + , "city" + , "civil" + , "claim" + , "clap" + , "clarify" + , "claw" + , "clay" + , "clean" + , "clerk" + , "clever" + , "click" + , "client" + , "cliff" + , "climb" + , "clinic" + , "clip" + , "clock" + , "clog" + , "close" + , "cloth" + , "cloud" + , "clown" + , "club" + , "clump" + , "cluster" + , "clutch" + , "coach" + , "coast" + , "coconut" + , "code" + , "coffee" + , "coil" + , "coin" + , "collect" + , "color" + , "column" + , "combine" + , "come" + , "comfort" + , "comic" + , "common" + , "company" + , "concert" + , "conduct" + , "confirm" + , "congress" + , "connect" + , "consider" + , "control" + , "convince" + , "cook" + , "cool" + , "copper" + , "copy" + , "coral" + , "core" + , "corn" + , "correct" + , "cost" + , "cotton" + , "couch" + , "country" + , "couple" + , "course" + , "cousin" + , "cover" + , "coyote" + , "crack" + , "cradle" + , "craft" + , "cram" + , "crane" + , "crash" + , "crater" + , "crawl" + , "crazy" + , "cream" + , "credit" + , "creek" + , "crew" + , "cricket" + , "crime" + , "crisp" + , "critic" + , "crop" + , "cross" + , "crouch" + , "crowd" + , "crucial" + , "cruel" + , "cruise" + , "crumble" + , "crunch" + , "crush" + , "cry" + , "crystal" + , "cube" + , "culture" + , "cup" + , "cupboard" + , "curious" + , "current" + , "curtain" + , "curve" + , "cushion" + , "custom" + , "cute" + , "cycle" + , "dad" + , "damage" + , "damp" + , "dance" + , "danger" + , "daring" + , "dash" + , "daughter" + , "dawn" + , "day" + , "deal" + , "debate" + , "debris" + , "decade" + , "december" + , "decide" + , "decline" + , "decorate" + , "decrease" + , "deer" + , "defense" + , "define" + , "defy" + , "degree" + , "delay" + , "deliver" + , "demand" + , "demise" + , "denial" + , "dentist" + , "deny" + , "depart" + , "depend" + , "deposit" + , "depth" + , "deputy" + , "derive" + , "describe" + , "desert" + , "design" + , "desk" + , "despair" + , "destroy" + , "detail" + , "detect" + , "develop" + , "device" + , "devote" + , "diagram" + , "dial" + , "diamond" + , "diary" + , "dice" + , "diesel" + , "diet" + , "differ" + , "digital" + , "dignity" + , "dilemma" + , "dinner" + , "dinosaur" + , "direct" + , "dirt" + , "disagree" + , "discover" + , "disease" + , "dish" + , "dismiss" + , "disorder" + , "display" + , "distance" + , "divert" + , "divide" + , "divorce" + , "dizzy" + , "doctor" + , "document" + , "dog" + , "doll" + , "dolphin" + , "domain" + , "donate" + , "donkey" + , "donor" + , "door" + , "dose" + , "double" + , "dove" + , "draft" + , "dragon" + , "drama" + , "drastic" + , "draw" + , "dream" + , "dress" + , "drift" + , "drill" + , "drink" + , "drip" + , "drive" + , "drop" + , "drum" + , "dry" + , "duck" + , "dumb" + , "dune" + , "during" + , "dust" + , "dutch" + , "duty" + , "dwarf" + , "dynamic" + , "eager" + , "eagle" + , "early" + , "earn" + , "earth" + , "easily" + , "east" + , "easy" + , "echo" + , "ecology" + , "economy" + , "edge" + , "edit" + , "educate" + , "effort" + , "egg" + , "eight" + , "either" + , "elbow" + , "elder" + , "electric" + , "elegant" + , "element" + , "elephant" + , "elevator" + , "elite" + , "else" + , "embark" + , "embody" + , "embrace" + , "emerge" + , "emotion" + , "employ" + , "empower" + , "empty" + , "enable" + , "enact" + , "end" + , "endless" + , "endorse" + , "enemy" + , "energy" + , "enforce" + , "engage" + , "engine" + , "enhance" + , "enjoy" + , "enlist" + , "enough" + , "enrich" + , "enroll" + , "ensure" + , "enter" + , "entire" + , "entry" + , "envelope" + , "episode" + , "equal" + , "equip" + , "era" + , "erase" + , "erode" + , "erosion" + , "error" + , "erupt" + , "escape" + , "essay" + , "essence" + , "estate" + , "eternal" + , "ethics" + , "evidence" + , "evil" + , "evoke" + , "evolve" + , "exact" + , "example" + , "excess" + , "exchange" + , "excite" + , "exclude" + , "excuse" + , "execute" + , "exercise" + , "exhaust" + , "exhibit" + , "exile" + , "exist" + , "exit" + , "exotic" + , "expand" + , "expect" + , "expire" + , "explain" + , "expose" + , "express" + , "extend" + , "extra" + , "eye" + , "eyebrow" + , "fabric" + , "face" + , "faculty" + , "fade" + , "faint" + , "faith" + , "fall" + , "false" + , "fame" + , "family" + , "famous" + , "fan" + , "fancy" + , "fantasy" + , "farm" + , "fashion" + , "fat" + , "fatal" + , "father" + , "fatigue" + , "fault" + , "favorite" + , "feature" + , "february" + , "federal" + , "fee" + , "feed" + , "feel" + , "female" + , "fence" + , "festival" + , "fetch" + , "fever" + , "few" + , "fiber" + , "fiction" + , "field" + , "figure" + , "file" + , "film" + , "filter" + , "final" + , "find" + , "fine" + , "finger" + , "finish" + , "fire" + , "firm" + , "first" + , "fiscal" + , "fish" + , "fit" + , "fitness" + , "fix" + , "flag" + , "flame" + , "flash" + , "flat" + , "flavor" + , "flee" + , "flight" + , "flip" + , "float" + , "flock" + , "floor" + , "flower" + , "fluid" + , "flush" + , "fly" + , "foam" + , "focus" + , "fog" + , "foil" + , "fold" + , "follow" + , "food" + , "foot" + , "force" + , "forest" + , "forget" + , "fork" + , "fortune" + , "forum" + , "forward" + , "fossil" + , "foster" + , "found" + , "fox" + , "fragile" + , "frame" + , "frequent" + , "fresh" + , "friend" + , "fringe" + , "frog" + , "front" + , "frost" + , "frown" + , "frozen" + , "fruit" + , "fuel" + , "fun" + , "funny" + , "furnace" + , "fury" + , "future" + , "gadget" + , "gain" + , "galaxy" + , "gallery" + , "game" + , "gap" + , "garage" + , "garbage" + , "garden" + , "garlic" + , "garment" + , "gas" + , "gasp" + , "gate" + , "gather" + , "gauge" + , "gaze" + , "general" + , "genius" + , "genre" + , "gentle" + , "genuine" + , "gesture" + , "ghost" + , "giant" + , "gift" + , "giggle" + , "ginger" + , "giraffe" + , "girl" + , "give" + , "glad" + , "glance" + , "glare" + , "glass" + , "glide" + , "glimpse" + , "globe" + , "gloom" + , "glory" + , "glove" + , "glow" + , "glue" + , "goat" + , "goddess" + , "gold" + , "good" + , "goose" + , "gorilla" + , "gospel" + , "gossip" + , "govern" + , "gown" + , "grab" + , "grace" + , "grain" + , "grant" + , "grape" + , "grass" + , "gravity" + , "great" + , "green" + , "grid" + , "grief" + , "grit" + , "grocery" + , "group" + , "grow" + , "grunt" + , "guard" + , "guess" + , "guide" + , "guilt" + , "guitar" + , "gun" + , "gym" + , "habit" + , "hair" + , "half" + , "hammer" + , "hamster" + , "hand" + , "happy" + , "harbor" + , "hard" + , "harsh" + , "harvest" + , "hat" + , "have" + , "hawk" + , "hazard" + , "head" + , "health" + , "heart" + , "heavy" + , "hedgehog" + , "height" + , "hello" + , "helmet" + , "help" + , "hen" + , "hero" + , "hidden" + , "high" + , "hill" + , "hint" + , "hip" + , "hire" + , "history" + , "hobby" + , "hockey" + , "hold" + , "hole" + , "holiday" + , "hollow" + , "home" + , "honey" + , "hood" + , "hope" + , "horn" + , "horror" + , "horse" + , "hospital" + , "host" + , "hotel" + , "hour" + , "hover" + , "hub" + , "huge" + , "human" + , "humble" + , "humor" + , "hundred" + , "hungry" + , "hunt" + , "hurdle" + , "hurry" + , "hurt" + , "husband" + , "hybrid" + , "ice" + , "icon" + , "idea" + , "identify" + , "idle" + , "ignore" + , "ill" + , "illegal" + , "illness" + , "image" + , "imitate" + , "immense" + , "immune" + , "impact" + , "impose" + , "improve" + , "impulse" + , "inch" + , "include" + , "income" + , "increase" + , "index" + , "indicate" + , "indoor" + , "industry" + , "infant" + , "inflict" + , "inform" + , "inhale" + , "inherit" + , "initial" + , "inject" + , "injury" + , "inmate" + , "inner" + , "innocent" + , "input" + , "inquiry" + , "insane" + , "insect" + , "inside" + , "inspire" + , "install" + , "intact" + , "interest" + , "into" + , "invest" + , "invite" + , "involve" + , "iron" + , "island" + , "isolate" + , "issue" + , "item" + , "ivory" + , "jacket" + , "jaguar" + , "jar" + , "jazz" + , "jealous" + , "jeans" + , "jelly" + , "jewel" + , "job" + , "join" + , "joke" + , "journey" + , "joy" + , "judge" + , "juice" + , "jump" + , "jungle" + , "junior" + , "junk" + , "just" + , "kangaroo" + , "keen" + , "keep" + , "ketchup" + , "key" + , "kick" + , "kid" + , "kidney" + , "kind" + , "kingdom" + , "kiss" + , "kit" + , "kitchen" + , "kite" + , "kitten" + , "kiwi" + , "knee" + , "knife" + , "knock" + , "know" + , "lab" + , "label" + , "labor" + , "ladder" + , "lady" + , "lake" + , "lamp" + , "language" + , "laptop" + , "large" + , "later" + , "latin" + , "laugh" + , "laundry" + , "lava" + , "law" + , "lawn" + , "lawsuit" + , "layer" + , "lazy" + , "leader" + , "leaf" + , "learn" + , "leave" + , "lecture" + , "left" + , "leg" + , "legal" + , "legend" + , "leisure" + , "lemon" + , "lend" + , "length" + , "lens" + , "leopard" + , "lesson" + , "letter" + , "level" + , "liar" + , "liberty" + , "library" + , "license" + , "life" + , "lift" + , "light" + , "like" + , "limb" + , "limit" + , "link" + , "lion" + , "liquid" + , "list" + , "little" + , "live" + , "lizard" + , "load" + , "loan" + , "lobster" + , "local" + , "lock" + , "logic" + , "lonely" + , "long" + , "loop" + , "lottery" + , "loud" + , "lounge" + , "love" + , "loyal" + , "lucky" + , "luggage" + , "lumber" + , "lunar" + , "lunch" + , "luxury" + , "lyrics" + , "machine" + , "mad" + , "magic" + , "magnet" + , "maid" + , "mail" + , "main" + , "major" + , "make" + , "mammal" + , "man" + , "manage" + , "mandate" + , "mango" + , "mansion" + , "manual" + , "maple" + , "marble" + , "march" + , "margin" + , "marine" + , "market" + , "marriage" + , "mask" + , "mass" + , "master" + , "match" + , "material" + , "math" + , "matrix" + , "matter" + , "maximum" + , "maze" + , "meadow" + , "mean" + , "measure" + , "meat" + , "mechanic" + , "medal" + , "media" + , "melody" + , "melt" + , "member" + , "memory" + , "mention" + , "menu" + , "mercy" + , "merge" + , "merit" + , "merry" + , "mesh" + , "message" + , "metal" + , "method" + , "middle" + , "midnight" + , "milk" + , "million" + , "mimic" + , "mind" + , "minimum" + , "minor" + , "minute" + , "miracle" + , "mirror" + , "misery" + , "miss" + , "mistake" + , "mix" + , "mixed" + , "mixture" + , "mobile" + , "model" + , "modify" + , "mom" + , "moment" + , "monitor" + , "monkey" + , "monster" + , "month" + , "moon" + , "moral" + , "more" + , "morning" + , "mosquito" + , "mother" + , "motion" + , "motor" + , "mountain" + , "mouse" + , "move" + , "movie" + , "much" + , "muffin" + , "mule" + , "multiply" + , "muscle" + , "museum" + , "mushroom" + , "music" + , "must" + , "mutual" + , "myself" + , "mystery" + , "myth" + , "naive" + , "name" + , "napkin" + , "narrow" + , "nasty" + , "nation" + , "nature" + , "near" + , "neck" + , "need" + , "negative" + , "neglect" + , "neither" + , "nephew" + , "nerve" + , "nest" + , "net" + , "network" + , "neutral" + , "never" + , "news" + , "next" + , "nice" + , "night" + , "noble" + , "noise" + , "nominee" + , "noodle" + , "normal" + , "north" + , "nose" + , "notable" + , "note" + , "nothing" + , "notice" + , "novel" + , "now" + , "nuclear" + , "number" + , "nurse" + , "nut" + , "oak" + , "obey" + , "object" + , "oblige" + , "obscure" + , "observe" + , "obtain" + , "obvious" + , "occur" + , "ocean" + , "october" + , "odor" + , "off" + , "offer" + , "office" + , "often" + , "oil" + , "okay" + , "old" + , "olive" + , "olympic" + , "omit" + , "once" + , "one" + , "onion" + , "online" + , "only" + , "open" + , "opera" + , "opinion" + , "oppose" + , "option" + , "orange" + , "orbit" + , "orchard" + , "order" + , "ordinary" + , "organ" + , "orient" + , "original" + , "orphan" + , "ostrich" + , "other" + , "outdoor" + , "outer" + , "output" + , "outside" + , "oval" + , "oven" + , "over" + , "own" + , "owner" + , "oxygen" + , "oyster" + , "ozone" + , "pact" + , "paddle" + , "page" + , "pair" + , "palace" + , "palm" + , "panda" + , "panel" + , "panic" + , "panther" + , "paper" + , "parade" + , "parent" + , "park" + , "parrot" + , "party" + , "pass" + , "patch" + , "path" + , "patient" + , "patrol" + , "pattern" + , "pause" + , "pave" + , "payment" + , "peace" + , "peanut" + , "pear" + , "peasant" + , "pelican" + , "pen" + , "penalty" + , "pencil" + , "people" + , "pepper" + , "perfect" + , "permit" + , "person" + , "pet" + , "phone" + , "photo" + , "phrase" + , "physical" + , "piano" + , "picnic" + , "picture" + , "piece" + , "pig" + , "pigeon" + , "pill" + , "pilot" + , "pink" + , "pioneer" + , "pipe" + , "pistol" + , "pitch" + , "pizza" + , "place" + , "planet" + , "plastic" + , "plate" + , "play" + , "please" + , "pledge" + , "pluck" + , "plug" + , "plunge" + , "poem" + , "poet" + , "point" + , "polar" + , "pole" + , "police" + , "pond" + , "pony" + , "pool" + , "popular" + , "portion" + , "position" + , "possible" + , "post" + , "potato" + , "pottery" + , "poverty" + , "powder" + , "power" + , "practice" + , "praise" + , "predict" + , "prefer" + , "prepare" + , "present" + , "pretty" + , "prevent" + , "price" + , "pride" + , "primary" + , "print" + , "priority" + , "prison" + , "private" + , "prize" + , "problem" + , "process" + , "produce" + , "profit" + , "program" + , "project" + , "promote" + , "proof" + , "property" + , "prosper" + , "protect" + , "proud" + , "provide" + , "public" + , "pudding" + , "pull" + , "pulp" + , "pulse" + , "pumpkin" + , "punch" + , "pupil" + , "puppy" + , "purchase" + , "purity" + , "purpose" + , "purse" + , "push" + , "put" + , "puzzle" + , "pyramid" + , "quality" + , "quantum" + , "quarter" + , "question" + , "quick" + , "quit" + , "quiz" + , "quote" + , "rabbit" + , "raccoon" + , "race" + , "rack" + , "radar" + , "radio" + , "rail" + , "rain" + , "raise" + , "rally" + , "ramp" + , "ranch" + , "random" + , "range" + , "rapid" + , "rare" + , "rate" + , "rather" + , "raven" + , "raw" + , "razor" + , "ready" + , "real" + , "reason" + , "rebel" + , "rebuild" + , "recall" + , "receive" + , "recipe" + , "record" + , "recycle" + , "reduce" + , "reflect" + , "reform" + , "refuse" + , "region" + , "regret" + , "regular" + , "reject" + , "relax" + , "release" + , "relief" + , "rely" + , "remain" + , "remember" + , "remind" + , "remove" + , "render" + , "renew" + , "rent" + , "reopen" + , "repair" + , "repeat" + , "replace" + , "report" + , "require" + , "rescue" + , "resemble" + , "resist" + , "resource" + , "response" + , "result" + , "retire" + , "retreat" + , "return" + , "reunion" + , "reveal" + , "review" + , "reward" + , "rhythm" + , "rib" + , "ribbon" + , "rice" + , "rich" + , "ride" + , "ridge" + , "rifle" + , "right" + , "rigid" + , "ring" + , "riot" + , "ripple" + , "risk" + , "ritual" + , "rival" + , "river" + , "road" + , "roast" + , "robot" + , "robust" + , "rocket" + , "romance" + , "roof" + , "rookie" + , "room" + , "rose" + , "rotate" + , "rough" + , "round" + , "route" + , "royal" + , "rubber" + , "rude" + , "rug" + , "rule" + , "run" + , "runway" + , "rural" + , "sad" + , "saddle" + , "sadness" + , "safe" + , "sail" + , "salad" + , "salmon" + , "salon" + , "salt" + , "salute" + , "same" + , "sample" + , "sand" + , "satisfy" + , "satoshi" + , "sauce" + , "sausage" + , "save" + , "say" + , "scale" + , "scan" + , "scare" + , "scatter" + , "scene" + , "scheme" + , "school" + , "science" + , "scissors" + , "scorpion" + , "scout" + , "scrap" + , "screen" + , "script" + , "scrub" + , "sea" + , "search" + , "season" + , "seat" + , "second" + , "secret" + , "section" + , "security" + , "seed" + , "seek" + , "segment" + , "select" + , "sell" + , "seminar" + , "senior" + , "sense" + , "sentence" + , "series" + , "service" + , "session" + , "settle" + , "setup" + , "seven" + , "shadow" + , "shaft" + , "shallow" + , "share" + , "shed" + , "shell" + , "sheriff" + , "shield" + , "shift" + , "shine" + , "ship" + , "shiver" + , "shock" + , "shoe" + , "shoot" + , "shop" + , "short" + , "shoulder" + , "shove" + , "shrimp" + , "shrug" + , "shuffle" + , "shy" + , "sibling" + , "sick" + , "side" + , "siege" + , "sight" + , "sign" + , "silent" + , "silk" + , "silly" + , "silver" + , "similar" + , "simple" + , "since" + , "sing" + , "siren" + , "sister" + , "situate" + , "six" + , "size" + , "skate" + , "sketch" + , "ski" + , "skill" + , "skin" + , "skirt" + , "skull" + , "slab" + , "slam" + , "sleep" + , "slender" + , "slice" + , "slide" + , "slight" + , "slim" + , "slogan" + , "slot" + , "slow" + , "slush" + , "small" + , "smart" + , "smile" + , "smoke" + , "smooth" + , "snack" + , "snake" + , "snap" + , "sniff" + , "snow" + , "soap" + , "soccer" + , "social" + , "sock" + , "soda" + , "soft" + , "solar" + , "soldier" + , "solid" + , "solution" + , "solve" + , "someone" + , "song" + , "soon" + , "sorry" + , "sort" + , "soul" + , "sound" + , "soup" + , "source" + , "south" + , "space" + , "spare" + , "spatial" + , "spawn" + , "speak" + , "special" + , "speed" + , "spell" + , "spend" + , "sphere" + , "spice" + , "spider" + , "spike" + , "spin" + , "spirit" + , "split" + , "spoil" + , "sponsor" + , "spoon" + , "sport" + , "spot" + , "spray" + , "spread" + , "spring" + , "spy" + , "square" + , "squeeze" + , "squirrel" + , "stable" + , "stadium" + , "staff" + , "stage" + , "stairs" + , "stamp" + , "stand" + , "start" + , "state" + , "stay" + , "steak" + , "steel" + , "stem" + , "step" + , "stereo" + , "stick" + , "still" + , "sting" + , "stock" + , "stomach" + , "stone" + , "stool" + , "story" + , "stove" + , "strategy" + , "street" + , "strike" + , "strong" + , "struggle" + , "student" + , "stuff" + , "stumble" + , "style" + , "subject" + , "submit" + , "subway" + , "success" + , "such" + , "sudden" + , "suffer" + , "sugar" + , "suggest" + , "suit" + , "summer" + , "sun" + , "sunny" + , "sunset" + , "super" + , "supply" + , "supreme" + , "sure" + , "surface" + , "surge" + , "surprise" + , "surround" + , "survey" + , "suspect" + , "sustain" + , "swallow" + , "swamp" + , "swap" + , "swarm" + , "swear" + , "sweet" + , "swift" + , "swim" + , "swing" + , "switch" + , "sword" + , "symbol" + , "symptom" + , "syrup" + , "system" + , "table" + , "tackle" + , "tag" + , "tail" + , "talent" + , "talk" + , "tank" + , "tape" + , "target" + , "task" + , "taste" + , "tattoo" + , "taxi" + , "teach" + , "team" + , "tell" + , "ten" + , "tenant" + , "tennis" + , "tent" + , "term" + , "test" + , "text" + , "thank" + , "that" + , "theme" + , "then" + , "theory" + , "there" + , "they" + , "thing" + , "this" + , "thought" + , "three" + , "thrive" + , "throw" + , "thumb" + , "thunder" + , "ticket" + , "tide" + , "tiger" + , "tilt" + , "timber" + , "time" + , "tiny" + , "tip" + , "tired" + , "tissue" + , "title" + , "toast" + , "tobacco" + , "today" + , "toddler" + , "toe" + , "together" + , "toilet" + , "token" + , "tomato" + , "tomorrow" + , "tone" + , "tongue" + , "tonight" + , "tool" + , "tooth" + , "top" + , "topic" + , "topple" + , "torch" + , "tornado" + , "tortoise" + , "toss" + , "total" + , "tourist" + , "toward" + , "tower" + , "town" + , "toy" + , "track" + , "trade" + , "traffic" + , "tragic" + , "train" + , "transfer" + , "trap" + , "trash" + , "travel" + , "tray" + , "treat" + , "tree" + , "trend" + , "trial" + , "tribe" + , "trick" + , "trigger" + , "trim" + , "trip" + , "trophy" + , "trouble" + , "truck" + , "true" + , "truly" + , "trumpet" + , "trust" + , "truth" + , "try" + , "tube" + , "tuition" + , "tumble" + , "tuna" + , "tunnel" + , "turkey" + , "turn" + , "turtle" + , "twelve" + , "twenty" + , "twice" + , "twin" + , "twist" + , "two" + , "type" + , "typical" + , "ugly" + , "umbrella" + , "unable" + , "unaware" + , "uncle" + , "uncover" + , "under" + , "undo" + , "unfair" + , "unfold" + , "unhappy" + , "uniform" + , "unique" + , "unit" + , "universe" + , "unknown" + , "unlock" + , "until" + , "unusual" + , "unveil" + , "update" + , "upgrade" + , "uphold" + , "upon" + , "upper" + , "upset" + , "urban" + , "urge" + , "usage" + , "use" + , "used" + , "useful" + , "useless" + , "usual" + , "utility" + , "vacant" + , "vacuum" + , "vague" + , "valid" + , "valley" + , "valve" + , "van" + , "vanish" + , "vapor" + , "various" + , "vast" + , "vault" + , "vehicle" + , "velvet" + , "vendor" + , "venture" + , "venue" + , "verb" + , "verify" + , "version" + , "very" + , "vessel" + , "veteran" + , "viable" + , "vibrant" + , "vicious" + , "victory" + , "video" + , "view" + , "village" + , "vintage" + , "violin" + , "virtual" + , "virus" + , "visa" + , "visit" + , "visual" + , "vital" + , "vivid" + , "vocal" + , "voice" + , "void" + , "volcano" + , "volume" + , "vote" + , "voyage" + , "wage" + , "wagon" + , "wait" + , "walk" + , "wall" + , "walnut" + , "want" + , "warfare" + , "warm" + , "warrior" + , "wash" + , "wasp" + , "waste" + , "water" + , "wave" + , "way" + , "wealth" + , "weapon" + , "wear" + , "weasel" + , "weather" + , "web" + , "wedding" + , "weekend" + , "weird" + , "welcome" + , "west" + , "wet" + , "whale" + , "what" + , "wheat" + , "wheel" + , "when" + , "where" + , "whip" + , "whisper" + , "wide" + , "width" + , "wife" + , "wild" + , "will" + , "win" + , "window" + , "wine" + , "wing" + , "wink" + , "winner" + , "winter" + , "wire" + , "wisdom" + , "wise" + , "wish" + , "witness" + , "wolf" + , "woman" + , "wonder" + , "wood" + , "wool" + , "word" + , "work" + , "world" + , "worry" + , "worth" + , "wrap" + , "wreck" + , "wrestle" + , "wrist" + , "write" + , "wrong" + , "yard" + , "year" + , "yellow" + , "you" + , "young" + , "youth" + , "zebra" + , "zero" + , "zone" + , "zoo" + ] diff --git a/ppad-bip39.cabal b/ppad-bip39.cabal @@ -0,0 +1,69 @@ +cabal-version: 3.0 +name: ppad-bip39 +version: 0.1.0 +synopsis: BIP39 mnemonic codes. +license: MIT +license-file: LICENSE +author: Jared Tobin +maintainer: jared@ppad.tech +category: Cryptography +build-type: Simple +tested-with: GHC == 9.8.1 +extra-doc-files: CHANGELOG +description: + [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) + mnemonic codes for deterministic key generation. + +source-repository head + type: git + location: git.ppad.tech/bip39.git + +library + default-language: Haskell2010 + hs-source-dirs: lib + ghc-options: + -Wall + exposed-modules: + Crypto.KDF.BIP39 + build-depends: + base >= 4.9 && < 5 + , bytestring >= 0.9 && < 0.13 + , ppad-pbkdf + , ppad-sha256 + , ppad-sha512 + , primitive + +test-suite bip39-tests + type: exitcode-stdio-1.0 + default-language: Haskell2010 + hs-source-dirs: test + main-is: Main.hs + + ghc-options: + -rtsopts -Wall -O2 + + build-depends: + base + , array + , bytestring + , ppad-bip39 + , tasty + , tasty-hunit + +benchmark bip39-bench + type: exitcode-stdio-1.0 + default-language: Haskell2010 + hs-source-dirs: bench + main-is: Main.hs + + ghc-options: + -rtsopts -O2 -Wall + + build-depends: + base + , array + , bytestring + , criterion + , deepseq + , ppad-bip39 + diff --git a/test/Main.hs b/test/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = pure ()