packages feed

haskoin-core 1.6.0 → 1.6.1

raw patch · 4 files changed

+63/−24 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -5,6 +5,12 @@ 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). +## [1.6.1] - 2026-11-06++### Changed++- `encodeBase58`, `encodeBase58Check`, `cashAddrEncode`, `cash32encode` and `cash32encodeType`, and with them `addrToText` and the JSON form of addresses, are faster. Base58 divides its big integer by 58^10 instead of once per digit and builds its bytes directly instead of a `String`. CashAddr looks its characters up in a `ByteString` instead of with `(!!)` on a `String`, converts to five-bit groups without lists, and unrolls the generators of its checksum. A P2PKH address takes 1.1 µs instead of 5.6 µs in CashAddr and 4.3 µs instead of 5.9 µs in Base58. The output is the same: 120,061 encodings of random payloads are byte for byte the same before and after.+ ## [1.6.0] - 2026-11-06  ### Added
haskoin-core.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           haskoin-core-version:        1.6.0+version:        1.6.1 synopsis:       Bitcoin & Bitcoin Cash library for Haskell description:    Please see the README on GitHub at <https://github.com/jprupp/haskoin-core#readme> category:       Bitcoin, Finance, Network
src/Haskoin/Address/Base58.hs view
@@ -33,9 +33,10 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as E-import Data.Word (Word8)+import Data.Bits (shiftL, (.|.))+import Data.Word (Word64, Word8) import Haskoin.Crypto.Hash (checkSum32)-import Haskoin.Util.Helpers (bsToInteger, integerToBS)+import Haskoin.Util.Helpers (integerToBS) import Numeric (showIntAtBase)  -- | 'Base58' classic Bitcoin address format.@@ -71,7 +72,29 @@ -- | Encode an arbitrary-length 'Integer' into a 'Base58' string. Leading zeroes -- will not be part of the resulting string. encodeBase58I :: Integer -> Base58-encodeBase58I i = cs $ showIntAtBase 58 (chr . fromIntegral . b58) i ""+encodeBase58I i+  | i < 0 = cs $ showIntAtBase 58 (chr . fromIntegral . b58) i ""+  | otherwise = E.decodeLatin1 . B.pack . map b58 $ chunks i []+  where+    -- Divide by 58^10 rather than by 58, so that a number of n digits takes+    -- n/10 divisions of a big number and the rest is machine arithmetic.+    chunk = 58 ^ (10 :: Int) :: Integer+    chunks n acc+      | n < chunk = top (fromInteger n) acc+      | otherwise =+          let (q, r) = n `quotRem` chunk+           in chunks q (digits (10 :: Int) (fromInteger r) acc)+    -- the digits of a chunk, ten of them with leading zeros+    digits 0 _ acc = acc+    digits k w acc =+      let (q, r) = w `quotRem` (58 :: Word64)+       in digits (k - 1) q (fromIntegral r : acc)+    -- the digits of the leading chunk, without leading zeros+    top w acc+      | w < 58 = fromIntegral w : acc+      | otherwise =+          let (q, r) = w `quotRem` (58 :: Word64)+           in top q (fromIntegral r : acc)  -- | Decode the UTF-8 bytes of a 'Base58' string into an 'Integer'. Fails on -- the first byte that is not a 'Base58' digit.@@ -93,7 +116,7 @@     l = T.replicate (B.length z) "1" -- preserve leading 0's     r       | B.null b = T.empty-      | otherwise = encodeBase58I $ bsToInteger b+      | otherwise = encodeBase58I $ B.foldl' (\n w -> n `shiftL` 8 .|. toInteger w) 0 b  -- | Decode a 'Base58'-encoded 'Text' to a 'ByteString'. Returns 'Nothing' if -- the input contains any character outside the 'Base58' alphabet.
src/Haskoin/Address/CashAddr.hs view
@@ -49,7 +49,7 @@ import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T-import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Data.Text.Encoding (decodeLatin1, decodeUtf8, encodeUtf8) import Data.Word (Word64, Word8) import Haskoin.Network.Data (Network (cashAddrPrefix)) import Haskoin.Util.Helpers (convertBits)@@ -205,21 +205,28 @@       px = B.map (.&. 0x1f) (encodeUtf8 pfx)       pd = px <> B.singleton 0 <> b32 <> B.replicate checkSumLength 0       cs = cash32Polymod pd-      c32 = B.map f (b32 <> cs)-      f = fromIntegral . ord . (charset !!) . fromIntegral-  return (pfx <> ":" <> decodeUtf8 c32)+      c32 = B.map (B.index charsetBytes . fromIntegral) (b32 <> cs)+  return (pfx <> ":" <> decodeLatin1 c32) +-- | 'charset' as bytes, to look up characters in constant time.+charsetBytes :: ByteString+charsetBytes = C.pack charset+ -- | Convert base of 'ByteString' from eight bits per byte to five bits per -- byte, adding padding as necessary. toBase32 :: ByteString -> ByteString-toBase32 =-  B.pack-    . map fromIntegral-    -- With padding allowed the conversion always succeeds.-    . fromMaybe []-    . convertBits True 8 5-    . map fromIntegral-    . B.unpack+toBase32 bs = fst $ B.unfoldrN groups group 0+  where+    groups = (B.length bs * 8 + 4) `div` 5+    byte i+      | i < B.length bs = fromIntegral (B.index bs i) :: Word+      | otherwise = 0+    -- the five bits that start this many bits into the input, from the two+    -- bytes that hold them, padded with zeros after the end+    group bit =+      let i = bit `shiftR` 3+          pair = (byte i `shiftL` 8) .|. byte (i + 1)+       in Just (fromIntegral ((pair `shiftR` (11 - (bit .&. 7))) .&. 0x1f), bit + 5)  -- | Convert five-bit groups to the bytes they fill completely, dropping any -- bits left over at the end.@@ -270,17 +277,20 @@ cash32Polymod :: ByteString -> ByteString cash32Polymod v =   B.pack-    [fromIntegral (polymod `shiftR` (5 * (7 - i))) .&. 0x1f | i <- [0 .. 7]]+    [fromIntegral (polymod `shiftR` (5 * (7 - i))) .&. 0x1f | i <- [0 .. 7 :: Int]]   where     polymod = B.foldl' outer (1 :: Word64) v `xor` 1     outer c d =-      let c0 = (fromIntegral (c `shiftR` 35) :: Word8)+      let c0 = c `shiftR` 35           c' = ((c .&. 0x07ffffffff) `shiftL` 5) `xor` fromIntegral d-       in foldl' (inner c0) c' (zip [0 ..] generator)-    generator =-      [0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470]-    inner c0 c (b, g)-      | c0 `testBit` b = c `xor` g+       in g 4 0x1e4f43e470 c0+            . g 3 0xae2eabe2a8 c0+            . g 2 0xf33e5fb3c4 c0+            . g 1 0x79b76d99e2 c0+            $ g 0 0x98f2bc8e61 c0 c'+    g :: Int -> Word64 -> Word64 -> Word64 -> Word64+    g b gen c0 c+      | c0 `testBit` b = c `xor` gen       | otherwise = c  -- | Validate that polymod 'ByteString' (eight bytes) is equal to zero.