packages feed

haskoin-core 0.20.4 → 0.20.5

raw patch · 4 files changed

+166/−68 lines, 4 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Haskoin.Address.Bech32: Bech32 :: Bech32Encoding
+ Haskoin.Address.Bech32: Bech32m :: Bech32Encoding
+ Haskoin.Address.Bech32: bech32Const :: Bech32Encoding -> Word
+ Haskoin.Address.Bech32: data Bech32Encoding
+ Haskoin.Address.Bech32: instance GHC.Classes.Eq Haskoin.Address.Bech32.Bech32Encoding
+ Haskoin.Address.Bech32: instance GHC.Classes.Ord Haskoin.Address.Bech32.Bech32Encoding
+ Haskoin.Address.Bech32: instance GHC.Enum.Enum Haskoin.Address.Bech32.Bech32Encoding
+ Haskoin.Address.Bech32: instance GHC.Show.Show Haskoin.Address.Bech32.Bech32Encoding
- Haskoin.Address.Bech32: bech32Decode :: Bech32 -> Maybe (HRP, [Word5])
+ Haskoin.Address.Bech32: bech32Decode :: Bech32 -> Maybe (Bech32Encoding, HRP, [Word5])
- Haskoin.Address.Bech32: bech32Encode :: HRP -> [Word5] -> Maybe Bech32
+ Haskoin.Address.Bech32: bech32Encode :: Bech32Encoding -> HRP -> [Word5] -> Maybe Bech32

Files

CHANGELOG.md view
@@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.20.5+### Added+- Support Bech32m address format for Taproot.+ ## 0.20.4 ### Fixed - Add missing case for witness version.
haskoin-core.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack ----- hash: d57876572e143601ab9185c42f47f703460c4e8396b846f805068a5cff8fec3e+-- hash: 83e95434961483bbb306908fbc61269ebec705003a8994ca0a8a04d1e3f7de50  name:           haskoin-core-version:        0.20.4+version:        0.20.5 synopsis:       Bitcoin & Bitcoin Cash library for Haskell description:    Please see the README on GitHub at <https://github.com/haskoin/haskoin-core#readme> category:       Bitcoin, Finance, Network
src/Haskoin/Address/Bech32.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE MultiWayIf        #-} {-# LANGUAGE OverloadedStrings #-} {-| Module      : Haskoin.Address.Base58@@ -14,6 +15,8 @@     ( -- * Bech32       HRP     , Bech32+    , Bech32Encoding(..)+    , bech32Const     , Data     , bech32Encode     , bech32Decode@@ -41,6 +44,9 @@ import qualified Data.Text.Encoding    as E import           Data.Word             (Word8) +data Bech32Encoding = Bech32 | Bech32m+   deriving (Eq, Show, Ord, Enum)+ -- | Bech32 human-readable string. type Bech32 = Text @@ -110,17 +116,25 @@   where     hrpBytes = B.unpack $ E.encodeUtf8 hrp --- | Calculate checksum for a string of five-bit words.-bech32CreateChecksum :: HRP -> [Word5] -> [Word5]-bech32CreateChecksum hrp dat = [word5 (polymod .>>. i) | i <- [25,20 .. 0]]+bech32Const :: Bech32Encoding -> Word+bech32Const Bech32  = 0x00000001+bech32Const Bech32m = 0x2bc830a3++-- | Calculate Bech32 checksum for a string of five-bit words.+bech32CreateChecksum :: Bech32Encoding -> HRP -> [Word5] -> [Word5]+bech32CreateChecksum enc hrp dat = [word5 (polymod .>>. i) | i <- [25,20 .. 0]]   where     values = bech32HRPExpand hrp ++ dat-    polymod =-        bech32Polymod (values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]) `xor` 1+    w5 = values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]+    polymod = bech32Polymod w5 `xor` bech32Const enc --- | Verify checksum for a human-readable part and string of five-bit words.-bech32VerifyChecksum :: HRP -> [Word5] -> Bool-bech32VerifyChecksum hrp dat = bech32Polymod (bech32HRPExpand hrp ++ dat) == 1+-- | Verify Bech32 checksum for a human-readable part and string of five-bit words.+bech32VerifyChecksum :: HRP -> [Word5] -> Maybe Bech32Encoding+bech32VerifyChecksum hrp dat =+  let poly = bech32Polymod (bech32HRPExpand hrp ++ dat)+  in if | poly == bech32Const Bech32  -> Just Bech32+        | poly == bech32Const Bech32m -> Just Bech32m+        | otherwise                   -> Nothing  -- | Maximum length of a Bech32 result. maxBech32Length :: Int@@ -129,10 +143,10 @@ -- | Encode string of five-bit words into 'Bech32' using a provided -- human-readable part. Can fail if 'HRP' is invalid or result would be longer -- than 90 characters.-bech32Encode :: HRP -> [Word5] -> Maybe Bech32-bech32Encode hrp dat = do+bech32Encode :: Bech32Encoding -> HRP -> [Word5] -> Maybe Bech32+bech32Encode enc hrp dat = do     guard $ checkHRP hrp-    let dat' = dat ++ bech32CreateChecksum (T.toLower hrp) dat+    let dat' = dat ++ bech32CreateChecksum enc (T.toLower hrp) dat         rest = map (charset !) dat'         result = T.concat [T.toLower hrp, T.pack "1", T.pack rest]     guard $ T.length result <= maxBech32Length@@ -140,11 +154,12 @@  -- | Check that human-readable part is valid for a 'Bech32' string. checkHRP :: HRP -> Bool-checkHRP hrp = not (T.null hrp) && T.all (\char -> char >= '\x21' && char <= '\x7e') hrp+checkHRP hrp = not (T.null hrp) &&+               T.all (\char -> char >= '\x21' && char <= '\x7e') hrp  -- | Decode human-readable 'Bech32' string into a human-readable part and a -- string of five-bit words.-bech32Decode :: Bech32 -> Maybe (HRP, [Word5])+bech32Decode :: Bech32 -> Maybe (Bech32Encoding, HRP, [Word5]) bech32Decode bech32 = do     guard $ T.length bech32 <= maxBech32Length     guard $ T.toUpper bech32 == bech32 || lowerBech32 == bech32@@ -153,8 +168,8 @@     hrp' <- T.stripSuffix "1" hrp     guard $ checkHRP hrp'     dat' <- mapM charsetMap $ T.unpack dat-    guard $ bech32VerifyChecksum hrp' dat'-    return (hrp', take (T.length dat - 6) dat')+    enc <- bech32VerifyChecksum hrp' dat'+    return (enc, hrp', take (T.length dat - 6) dat')   where     lowerBech32 = T.toLower bech32 @@ -204,26 +219,28 @@     map fromIntegral <$> convertBits (map fromWord5 dat) 5 8 noPadding  -- | Check if witness version and program are valid.-segwitCheck :: Word8 -> Data -> Bool-segwitCheck witver witprog =+segwitCheck :: Bech32Encoding -> Word8 -> Data -> Bool+segwitCheck enc witver witprog =     witver <= 16 &&     if witver == 0-        then length witprog == 20 || length witprog == 32-        else length witprog >= 2 && length witprog <= 40+        then enc == Bech32  && (length witprog == 20 || length witprog == 32)+        else enc == Bech32m && (length witprog >= 2 && length witprog <= 40)  -- | Decode SegWit 'Bech32' address from a string and expected human-readable part. segwitDecode :: HRP -> Bech32 -> Maybe (Word8, Data) segwitDecode hrp addr = do-    (hrp', dat) <- bech32Decode addr+    (enc, hrp', dat) <- bech32Decode addr     guard $ (hrp == hrp') && not (null dat)     let (UnsafeWord5 witver:datBase32) = dat     decoded <- toBase256 datBase32-    guard $ segwitCheck witver decoded+    guard $ segwitCheck enc witver decoded     return (witver, decoded)  -- | Encode 'Data' as a SegWit 'Bech32' address. Needs human-readable part and -- witness program version. segwitEncode :: HRP -> Word8 -> Data -> Maybe Text segwitEncode hrp witver witprog = do-    guard $ segwitCheck witver witprog-    bech32Encode hrp $ UnsafeWord5 witver : toBase32 witprog+    guard $ segwitCheck enc witver witprog+    bech32Encode enc hrp $ UnsafeWord5 witver : toBase32 witprog+  where+    enc = if witver == 0 then Bech32 else Bech32m
test/Haskoin/Address/Bech32Spec.hs view
@@ -16,43 +16,51 @@ import           Haskoin.Address import           Haskoin.Address.Bech32 import           Haskoin.Util-import           Test.Hspec import           Test.HUnit+import           Test.Hspec  spec = do     describe "bech32 checksum" $ do-        it "should have valid checksum" $ forM_ validChecksums testValidChecksum-        it "should have invalid checksum" $+        it "should be valid" $+            forM_ validChecksums (uncurry testValidChecksum)+        it "should be invalid" $             forM_ invalidChecksums testInvalidChecksum-        it "should be a valid address" $ forM_ validAddresses testValidAddress-        it "should be an invalid address" $-            forM_ invalidAddresses testInvalidAddress-        it "should be same for the input in different case" $-            all (== Just "test12hrzfj") . map (flip bech32Encode []) $ hrpCaseVariants-    describe "more encoding/decoding cases" $ do-        it "length > 90" $-            assert $-            isNothing $-            bech32Encode "bc" (replicate 82 (word5 (1 :: Word8)))-        it "segwit version bounds" $+        it "should be case-insensitive" $+            all (== Just "test12hrzfj") $+            map (flip (bech32Encode Bech32) []) hrpCaseVariants+    describe "bech32 address" $ do+        it "should be valid" $+            forM_ validChecksums (uncurry testValidChecksum)+        it "should be invalid" $+            forM_ invalidChecksums testInvalidChecksum+        it "should be case-insensitive" $+            all (== Just "test12hrzfj") $+            map (flip (bech32Encode Bech32) []) hrpCaseVariants+    describe "bech32 encoding/decoding" $ do+        it "should not encode long data string" $+            assert . isNothing $+            bech32Encode Bech32 "bc" (replicate 82 (word5 (1 :: Word8)))+        it "should not encode bad version number" $             assert $ isNothing $ segwitEncode "bc" 17 []-        it "segwit prog len version 0" $+        it "should not encode invalid length for version 0" $             assert $ isNothing $ segwitEncode "bc" 0 (replicate 30 1)-        it "segwit prog len version != 0" $+        it "should relax length restrictions for versions other than 0" $             assert $ isJust $ segwitEncode "bc" 1 (replicate 30 1)-        it "segwit prog len version != 0" $+        it "should not encode another long data string" $             assert $ isNothing $ segwitEncode "bc" 1 (replicate 41 1)-        it "empty HRP encode" $ assert $ isNothing $ bech32Encode "" []-        it "empty HRP encode" $+        it "should not encode empty human readable part" $+            assert $ isNothing $ bech32Encode Bech32 "" []+        it "should not decode empty human-readable part" $             assert $ isNothing $ bech32Decode "10a06t8"-        it "hrp lowercased" $-            bech32Encode "HRP" [] `shouldBe` bech32Encode "hrp" []+        it "human-readable part should be case-insensitive" $+            bech32Encode Bech32 "HRP" [] `shouldBe` bech32Encode Bech32 "hrp" []  -testValidChecksum :: Bech32 -> Assertion-testValidChecksum checksum = case bech32Decode checksum of-    Nothing                      -> assertFailure (show checksum)-    Just (resultHRP, resultData) -> do+testValidChecksum :: Bech32Encoding -> Bech32 -> Assertion+testValidChecksum enc checksum = case bech32Decode checksum of+    Nothing                           -> assertFailure (show checksum)+    Just (enc', resultHRP, resultData) -> do+        assertEqual (show checksum ++ " encoding incorrect") enc enc'         -- test that a corrupted checksum fails decoding.         let (hrp, rest)         = T.breakOnEnd "1" checksum             Just (first, rest') = uncons rest@@ -60,7 +68,7 @@         assertBool (show checksum ++ " corrupted")             $ isNothing (bech32Decode checksumCorrupted)         -- test that re-encoding the decoded checksum results in the same checksum.-        let checksumEncoded  = bech32Encode resultHRP resultData+        let checksumEncoded  = bech32Encode enc' resultHRP resultData             expectedChecksum = Just $ T.toLower checksum         assertEqual (show checksum ++ " re-encode")                     expectedChecksum@@ -75,7 +83,8 @@     let address' = T.toLower address         hrp      = T.take 2 address'     case segwitDecode hrp address of-        Nothing                -> assertFailure "decode failed"+        Nothing                ->+            assertFailure (T.unpack address <> ": decode failed")         Just (witver, witprog) -> do             assertEqual (show address)                         (decodeHex hexscript)@@ -94,13 +103,44 @@     B.pack $ witver' : fromIntegral (length witprog) : witprog     where witver' = if witver == 0 then 0 else witver + 0x50 -validChecksums :: [Text]+validChecksums :: [(Bech32Encoding, Text)] validChecksums =-    [ "A12UEL5L"-    , "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"-    , "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"-    , "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"-    , "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"+    [ ( Bech32+      , "A12UEL5L"+      )+    , ( Bech32+      , "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"+      )+    , ( Bech32+      , "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"+      )+    , ( Bech32+      , "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"+      )+    , ( Bech32+      , "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"+      )+    , ( Bech32m+      , "A1LQFN3A"+      )+    , ( Bech32m+      , "a1lqfn3a"+      )+    , ( Bech32m+      , "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6"+      )+    , ( Bech32m+      , "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"+      )+    , ( Bech32m+      , "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8"+      )+    , ( Bech32m+      , "split1checkupstagehandshakeupstreamerranterredcaperredlc445v"+      )+    , ( Bech32m+      , "?1v759aa"+      )     ]  invalidChecksums :: [Text]@@ -117,15 +157,39 @@  validAddresses :: [(Text, Text)] validAddresses =-    [ ("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6")-    , ("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"-      ,"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262")-    , ("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"-      ,"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6")-    , ("BC1SW50QA3JX3S", "6002751e")-    , ("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323")-    , ("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"-      ,"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433")+    [ ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"+      , "0014751e76e8199196d454941c45d1b3a323f1433bd6"+      )+    , ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"+      , "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"+      )+    , ( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"+      , "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"+      )+    , ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"+      , "0014751e76e8199196d454941c45d1b3a323f1433bd6"+      )+    , ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"+      , "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"+      )+    , ( "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y"+      , "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"+      )+    , ( "BC1SW50QGDZ25J"+      , "6002751e"+      )+    , ( "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs"+      , "5210751e76e8199196d454941c45d1b3a323"+      )+    , ( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"+      , "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"+      )+    , ( "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c"+      , "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"+      )+    , ( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0"+      , "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"+      )     ]  invalidAddresses :: [Text]@@ -140,6 +204,19 @@     , "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"     , "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"     , "bc1gmk9yu"+    , "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"+    , "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"+    , "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf"+    , "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"+    , "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"+    , "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47"+    , "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4"+    , "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R"+    , "bc1pw5dgrnzv"+    , "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav"+    , "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq"+    , "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf"+    , "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"     ]  hrpCaseVariants:: [Text]