ppad-bip39 0.1.1 → 0.2.0
raw patch · 4 files changed
+94/−51 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Crypto.KDF.BIP39: seed_unsafe :: Text -> Text -> ByteString
Files
- CHANGELOG +5/−0
- lib/Crypto/KDF/BIP39.hs +81/−46
- ppad-bip39.cabal +1/−1
- test/Main.hs +7/−4
CHANGELOG view
@@ -1,5 +1,10 @@ # Changelog +- 0.2.0 (2025-02-27)+ * 'seed' now validates the words in the mnemonic supplied to it;+ 'seed_unsafe' has been added as a function that works for any+ wordlist.+ - 0.1.1 (2025-02-27) * Include wordlists in generated Haddocks.
lib/Crypto/KDF/BIP39.hs view
@@ -22,6 +22,7 @@ -- * Seed derivation , seed , _seed+ , seed_unsafe -- * Wordlists , Wordlist(..)@@ -65,15 +66,17 @@ -- | Generate a BIP39 mnemonic from some entropy, using the default English -- wordlist. ----- The entropy must be at least 128 bits long, at most 256 bits long,--- and its length must be a multiple of 32 bits. Providing invalid--- entropy will result in an 'ErrorCall' exception.+-- The entropy must be at least 128 bits long and at most 256 bits+-- long. Providing invalid entropy will result in an 'ErrorCall'+-- exception. -- -- >>> import qualified System.Entropy as E -- >>> trop <- E.getEntropy 16 -- >>> mnemonic trop -- "coral maze mimic half fat breeze thought club give brass bone snake"-mnemonic :: BS.ByteString -> T.Text+mnemonic+ :: BS.ByteString -- ^ 128-256 bits of entropy+ -> T.Text mnemonic = _mnemonic english -- | Generate a BIP39 mnemonic from some entropy, using the provided@@ -87,7 +90,10 @@ -- >>> trop <- E.getEntropy 16 -- >>> _mnemonic czech trop -- "naslepo lysina dikobraz slupka beseda rorejs ostraha kobliha napevno blahobyt kazivost jiskra"-_mnemonic :: Wordlist -> BS.ByteString -> T.Text+_mnemonic+ :: Wordlist+ -> BS.ByteString -- ^ 128-256 bits of entropy+ -> T.Text _mnemonic (Wordlist wlist) entropy@(BI.PS _ _ l) | l < 16 = error "ppad-bip39 (mnemonic): invalid entropy length" | l > 32 = error "ppad-bip39 (mnemonic): invalid entropy length"@@ -124,6 +130,69 @@ Nothing {-# INLINE words #-} +-- | Derive a master seed from a provided mnemonic and passphrase, where the+-- mnemonic has been generated from the default English wordlist.+--+-- The mnemonic's length and words are validated. If you want to+-- validate the mnemonic's words against a non-English wordlist, use+-- '_seed'.+--+-- >>> let mnem = "coral maze mimic half fat breeze thought club give brass bone snake"+-- >> let pass = "hunter2"+-- >>> seed mnem pass+-- <512-bit long seed>+seed+ :: T.Text -- ^ mnemonic+ -> T.Text -- ^ passphrase (use e.g. "" or 'mempty' if not required)+ -> BS.ByteString -- ^ seed+seed = _seed english++-- | Derive a master seed from a provided mnemonic and passphrase, where the+-- mnemonic has been generated from an arbitrary wordlist.+--+-- The provided mnemonic is checked for validity using '_valid'.+-- Providing an invalid mnemonic will result in an 'ErrorCall'+-- exception.+--+-- >>> let mnem = "coral maze mimic half fat breeze thought club give brass bone snake"+-- >> let pass = "hunter2"+-- >>> _seed english mnem pass+-- <512-bit long seed>+_seed+ :: Wordlist -- ^ wordlist+ -> T.Text -- ^ mnemonic+ -> T.Text -- ^ passphrase (use e.g. "" or 'mempty' if not required)+ -> BS.ByteString -- ^ seed+_seed wlist mnem pass+ | not (_valid wlist mnem) =+ error "ppad-bip39 (seed): invalid mnemonic"+ | otherwise =+ let salt = TE.encodeUtf8 ("mnemonic" <> ICU.nfkd pass)+ norm = TE.encodeUtf8 (ICU.nfkd mnem)+ in PBKDF.derive SHA512.hmac norm salt 2048 64 where+{-# INLINE _seed #-}++-- | Derive a master seed from a provided mnemonic and passphrase.+--+-- The mnemonic's length is validated, but its individual words are+-- /not/. This function thus works for every wordlist.+--+-- >>> let mnem = "coral maze mimic half fat breeze thought club give brass bone snake"+-- >> let pass = "hunter2"+-- >>> seed_unsafe mnem pass+-- <512-bit long seed>+seed_unsafe+ :: T.Text -- ^ mnemonic+ -> T.Text -- ^ passphrase (use e.g. "" or 'mempty' if not required)+ -> BS.ByteString -- ^ seed+seed_unsafe mnem pass+ | length (T.words mnem) `notElem` [12, 15, 18, 21, 24] =+ error "ppad-bip39 (seed_unsafe): invalid mnemonic"+ | otherwise =+ let salt = TE.encodeUtf8 ("mnemonic" <> ICU.nfkd pass)+ norm = TE.encodeUtf8 (ICU.nfkd mnem)+ in PBKDF.derive SHA512.hmac norm salt 2048 64 where+ -- | Validate a mnemonic against the default English wordlist. -- -- Verifies that the mnemonic has a valid length, and that every word@@ -133,7 +202,9 @@ -- True -- >>> valid "coral maze mimic half fat breeze thought club give brass bone" -- False-valid :: T.Text -> Bool+valid+ :: T.Text -- ^ mnemonic+ -> Bool -- ^ 'True' if valid valid mnem = length ws `elem` [12, 15, 18, 21, 24] && all M.isJust (fmap (\word -> F.find (== word) wlist) ws)@@ -151,51 +222,15 @@ -- True -- >>> _valid chinese_simplified mnem -- False-_valid :: Wordlist -> T.Text -> Bool+_valid+ :: Wordlist+ -> T.Text -- ^ mnemonic+ -> Bool -- ^ 'True' if valid _valid (Wordlist wlist) mnem = length ws `elem` [12, 15, 18, 21, 24] && all M.isJust (fmap (\word -> F.find (== word) wlist) ws) where ws = T.words mnem---- | Derive a master seed from a provided mnemonic and passphrase.------ The mnemonic's length is validated, but its individual words are--- /not/. If you want to validate the mnemonic's words against a--- wordlist, use '_seed'.------ >>> let mnem = "coral maze mimic half fat breeze thought club give brass bone snake"--- >> let pass = "hunter2"--- >>> seed mnem pass--- <512-bit long seed>-seed :: T.Text -> T.Text -> BS.ByteString-seed mnem pass- | length (T.words mnem) `notElem` [12, 15, 18, 21, 24] =- error "ppad-bip39 (seed): invalid mnemonic"- | otherwise =- let salt = TE.encodeUtf8 ("mnemonic" <> ICU.nfkd pass)- norm = TE.encodeUtf8 (ICU.nfkd mnem)- in PBKDF.derive SHA512.hmac norm salt 2048 64 where---- | Derive a master seed from a provided mnemonic and passphrase, where the--- mnemonic has been generated from an arbitrary wordlist.------ The provided mnemonic is checked for validity using '_valid'.--- Providing an invalid mnemonic will result in an 'ErrorCall'--- exception.------ >>> let mnem = "coral maze mimic half fat breeze thought club give brass bone snake"--- >> let pass = "hunter2"--- >>> _seed english mnem pass--- <512-bit long seed>-_seed :: Wordlist -> T.Text -> T.Text -> BS.ByteString-_seed wlist mnem pass- | not (_valid wlist mnem) =- error "ppad-bip39 (seed): invalid mnemonic"- | otherwise =- let salt = TE.encodeUtf8 ("mnemonic" <> ICU.nfkd pass)- norm = TE.encodeUtf8 (ICU.nfkd mnem)- in PBKDF.derive SHA512.hmac norm salt 2048 64 where -- wordlists ------------------------------------------------------------------
ppad-bip39.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: ppad-bip39-version: 0.1.1+version: 0.2.0 synopsis: BIP39 mnemonic codes. license: MIT license-file: LICENSE
test/Main.hs view
@@ -76,8 +76,8 @@ seed = bt_seed xprv = bt_xprv out_mnem = BIP39._mnemonic wl entr- giv_seed = BIP39.seed mnem "TREZOR"- out_seed = BIP39.seed out_mnem "TREZOR"+ giv_seed = seed_fn mnem "TREZOR"+ out_seed = seed_fn out_mnem "TREZOR" out_xprv = case BIP32.master out_seed of Just hd -> BIP32.xprv hd Nothing -> error "bang (bip32)"@@ -93,6 +93,9 @@ , testCase "xprv" $ assertEqual mempty xprv out_xprv ] where+ seed_fn = case wlist of+ English -> BIP39.seed+ _ -> BIP39.seed_unsafe wl = case wlist of English -> BIP39.english ChineseTraditional -> BIP39.chinese_traditional@@ -113,8 +116,8 @@ seed = jp_seed xprv = jp_xprv out_mnem = BIP39._mnemonic BIP39.japanese entr- giv_seed = BIP39.seed mnem pass- out_seed = BIP39.seed out_mnem pass+ giv_seed = BIP39.seed_unsafe mnem pass+ out_seed = BIP39.seed_unsafe out_mnem pass out_xprv = case BIP32.master out_seed of Just hd -> BIP32.xprv hd Nothing -> error "bang (bip32, jp)"