commit bc25954dd7207249d83438d092619d4c0e28514a
Author: Jared Tobin <jared@jtobin.io>
Date: Wed, 26 Feb 2025 20:07:31 +0400
lib: init
Diffstat:
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 ()