bip39

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

commit bd86792ed179e28005ea5c858567fc177086b34a
parent 83cd6c84acd6f527a8e1fb1bf7c66e8cbacb5cf4
Author: Jared Tobin <jared@jtobin.io>
Date:   Thu, 27 Feb 2025 08:21:51 +0400

test: bip39 vectors, passing for mnemonic

Diffstat:
Mflake.lock | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mflake.nix | 11+++++++++++
Mppad-bip39.cabal | 6++++++
Mtest/Main.hs | 38+++++++++++++++++++++++++++++++++++++-
Atest/Vectors.hs | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 313 insertions(+), 1 deletion(-)

diff --git a/flake.lock b/flake.lock @@ -99,6 +99,134 @@ "url": "git://git.ppad.tech/base16.git" } }, + "ppad-base58": { + "inputs": { + "flake-utils": [ + "ppad-bip32", + "ppad-base58", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-bip32", + "ppad-base58", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-bip32", + "ppad-nixpkgs" + ], + "ppad-sha256": [ + "ppad-bip32", + "ppad-sha256" + ] + }, + "locked": { + "lastModified": 1739979602, + "narHash": "sha256-RrhqNPuDmKo1mG0Y2ZyaeuMuCjtIhGLLO87TaJeEDZI=", + "ref": "master", + "rev": "40e80c1b2c39c829976aab44f6d318ebe4e2e784", + "revCount": 22, + "type": "git", + "url": "git://git.ppad.tech/base58.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/base58.git" + } + }, + "ppad-bip32": { + "inputs": { + "flake-utils": [ + "ppad-bip32", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-bip32", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-base16": [ + "ppad-base16" + ], + "ppad-base58": "ppad-base58", + "ppad-nixpkgs": [ + "ppad-nixpkgs" + ], + "ppad-ripemd160": "ppad-ripemd160", + "ppad-secp256k1": "ppad-secp256k1", + "ppad-sha256": [ + "ppad-sha256" + ], + "ppad-sha512": [ + "ppad-sha512" + ] + }, + "locked": { + "lastModified": 1740292997, + "narHash": "sha256-EOmvT1aBIVfSlleDn1Qg219rF6FgHovu/ZWGgfcYfqc=", + "ref": "master", + "rev": "ce0978beadab34fd1103287a99c1e3f335f5f58a", + "revCount": 37, + "type": "git", + "url": "git://git.ppad.tech/bip32.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/bip32.git" + } + }, + "ppad-hmac-drbg": { + "inputs": { + "flake-utils": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-hmac-drbg", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-hmac-drbg", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-nixpkgs" + ], + "ppad-sha256": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-sha256" + ], + "ppad-sha512": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-sha512" + ] + }, + "locked": { + "lastModified": 1737297956, + "narHash": "sha256-3/jNY1Qd1dIYUEQSH47xJxvgg5dS6fVFWwxasgcI9OA=", + "ref": "master", + "rev": "27a88d0f011578171aee824ef838dfbf60fa6898", + "revCount": 43, + "type": "git", + "url": "git://git.ppad.tech/hmac-drbg.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/hmac-drbg.git" + } + }, "ppad-nixpkgs": { "inputs": { "flake-utils": "flake-utils", @@ -157,6 +285,83 @@ "url": "git://git.ppad.tech/pbkdf.git" } }, + "ppad-ripemd160": { + "inputs": { + "flake-utils": [ + "ppad-bip32", + "ppad-ripemd160", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-bip32", + "ppad-ripemd160", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-bip32", + "ppad-nixpkgs" + ] + }, + "locked": { + "lastModified": 1738846817, + "narHash": "sha256-mjefp2TM88Psw8moP0ioHHaYOrG0n8jEN1d5hjvTE6k=", + "ref": "master", + "rev": "d46ac86d87779e651b7a1d2f36ca03beaa08574d", + "revCount": 22, + "type": "git", + "url": "git://git.ppad.tech/ripemd160.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/ripemd160.git" + } + }, + "ppad-secp256k1": { + "inputs": { + "flake-utils": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-bip32", + "ppad-secp256k1", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-hmac-drbg": "ppad-hmac-drbg", + "ppad-nixpkgs": [ + "ppad-bip32", + "ppad-nixpkgs" + ], + "ppad-sha256": [ + "ppad-bip32", + "ppad-sha256" + ], + "ppad-sha512": [ + "ppad-bip32", + "ppad-sha512" + ] + }, + "locked": { + "lastModified": 1739709462, + "narHash": "sha256-bgdKy8Cx67DeNxIANAxFnelovj2LP4opaprkWp5KwA4=", + "ref": "master", + "rev": "0c075b2ca6b95f98924fa76b278402f089e33f71", + "revCount": 138, + "type": "git", + "url": "git://git.ppad.tech/secp256k1.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/secp256k1.git" + } + }, "ppad-sha256": { "inputs": { "flake-utils": [ @@ -230,6 +435,7 @@ "nixpkgs" ], "ppad-base16": "ppad-base16", + "ppad-bip32": "ppad-bip32", "ppad-nixpkgs": "ppad-nixpkgs", "ppad-pbkdf": "ppad-pbkdf", "ppad-sha256": "ppad-sha256", diff --git a/flake.nix b/flake.nix @@ -7,6 +7,15 @@ url = "git://git.ppad.tech/nixpkgs.git"; ref = "master"; }; + ppad-bip32 = { + type = "git"; + url = "git://git.ppad.tech/bip32.git"; + ref = "master"; + inputs.ppad-nixpkgs.follows = "ppad-nixpkgs"; + inputs.ppad-base16.follows = "ppad-base16"; + inputs.ppad-sha256.follows = "ppad-sha256"; + inputs.ppad-sha512.follows = "ppad-sha512"; + }; ppad-base16 = { type = "git"; url = "git://git.ppad.tech/base16.git"; @@ -40,6 +49,7 @@ outputs = { self, nixpkgs, flake-utils, ppad-nixpkgs , ppad-sha256, ppad-sha512 , ppad-base16 + , ppad-bip32 , ppad-pbkdf }: flake-utils.lib.eachDefaultSystem (system: @@ -51,6 +61,7 @@ hpkgs = pkgs.haskell.packages.ghc981.extend (new: old: { ${lib} = old.callCabal2nixWithOptions lib ./. "--enable-profiling" {}; + ppad-bip32 = ppad-bip32.packages.${system}.default; ppad-base16 = ppad-base16.packages.${system}.default; ppad-sha256 = ppad-sha256.packages.${system}.default; ppad-sha512 = ppad-sha512.packages.${system}.default; diff --git a/ppad-bip39.cabal b/ppad-bip39.cabal @@ -38,18 +38,24 @@ test-suite bip39-tests default-language: Haskell2010 hs-source-dirs: test main-is: Main.hs + other-modules: + Vectors ghc-options: -rtsopts -Wall -O2 build-depends: base + , aeson , array , bytestring , ppad-base16 + , ppad-bip32 , ppad-bip39 , tasty , tasty-hunit + , text + , vector benchmark bip39-bench type: exitcode-stdio-1.0 diff --git a/test/Main.hs b/test/Main.hs @@ -1,4 +1,40 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} + module Main where +import qualified Crypto.KDF.BIP39 as BIP39 +import qualified Data.ByteString as BS +import qualified Data.Aeson as A +import qualified Data.Text.IO as TIO +import Test.Tasty +import Test.Tasty.HUnit +import qualified Vectors as V + main :: IO () -main = pure () +main = do + vectors_bip39 <- TIO.readFile "etc/vectors.json" + let vectors = + A.decodeStrictText vectors_bip39 :: Maybe V.Vectors + case vectors of + Nothing -> error "couldn't parse bip39 vectors" + Just vs -> defaultMain $ testGroup "ppad-bip39" [ + bip39_tests vs + ] + +bip39_tests :: V.Vectors -> TestTree +bip39_tests V.Vectors {..} = + testGroup "bip39 vectors" $ + fmap execute v_wordlist + +execute :: V.Bip39Test -> TestTree +execute V.Bip39Test {..} = do + let entr = bt_entropy + mnem = bt_mnemonic + seed = bt_seed + xprv = bt_xprv + BIP39.Mnemonic out = BIP39.mnemonic entr + t_msg = "mnemonic " <> show mnem + testCase t_msg $ + assertEqual mempty mnem out + diff --git a/test/Vectors.hs b/test/Vectors.hs @@ -0,0 +1,53 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} + +module Vectors ( + Vectors(..) + , Bip39Test(..) + ) where + +import Data.Aeson ((.:)) +import qualified Data.Aeson as A +import qualified Data.ByteString as BS +import qualified Data.ByteString.Base16 as B16 +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE +import Data.Word (Word32, Word64) +import qualified Data.Vector as V + +data Vectors = Vectors { + v_wordlist :: ![Bip39Test] + } deriving Show + +instance A.FromJSON Vectors where + parseJSON = A.withObject "Vectors" $ \m -> Vectors + <$> m .: "english" + +data Bip39Test = Bip39Test { + bt_entropy :: !BS.ByteString + , bt_mnemonic :: !BS.ByteString + , bt_seed :: !BS.ByteString + , bt_xprv :: !BS.ByteString + } deriving Show + +decodehex :: T.Text -> BS.ByteString +decodehex t = case B16.decode (TE.encodeUtf8 t) of + Nothing -> error "bang (decodehex)" + Just bs -> bs + +instance A.FromJSON Bip39Test where + parseJSON = A.withArray "Bip39Test" $ \m -> + let bt_entropy = case m V.! 0 of + A.String t -> decodehex t + _ -> error "bang (entropy)" + bt_mnemonic = case m V.! 1 of + A.String t -> TE.encodeUtf8 t + _ -> error "bang (mnemonic)" + bt_seed = case m V.! 2 of + A.String t -> decodehex t + _ -> error "bang (seed)" + bt_xprv = case m V.! 3 of + A.String t -> TE.encodeUtf8 t + _ -> error "bang (xprv)" + in pure Bip39Test {..} +