Bitcoin/address validation: Difference between revisions

Line 895:
</pre>
=={{header|Haskell}}==
<lang haskell>import Data Control.ListMonad (unfoldr, elemIndex (when)
import Data.BinaryList (Word8elemIndex)
import Crypto Data.Hash.SHA256Monoid ((hash<>))
import qualified Data.ByteString (unpack, pack) as BS
import Data.ByteString (ByteString)
 
import Crypto.Hash.SHA256 (hash) -- from package cryptohash
 
-- Convert from base58 encoded value to Integer
decode58 :: String -> Maybe Integer
decode58 = fmap combine . traverse parseDigit
decode58 = foldl (\v d -> (+) <$> ((58*) <$> v) <*> (fromIntegral <$> elemIndex d c58 )) $ Just 0
where
where c58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
combine = foldl (\acc digit -> 58 * acc + digit) 0 -- should be foldl', but this trips up the highlighting
parseDigit char = toInteger <$> elemIndex char c58
where c58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
 
-- Convert from base58 encoded value to list of bytes
toBytes :: Integer -> [Word8]ByteString
toBytes = BS.reverse . BS.pack . map (fromIntegral . (`mod` 256)) . takeWhile (> 0) . iterate (`div` 256)
 
-- Check if the hash of the first 21 (padded) bytes matches the last 4 bytes
checksumValid :: ByteString -> Bool
checksumValid address =
let (value, checksum) = BS.splitAt 21 $ leftPad address
in and $ BS.zipWith (==) checksum $ hash $ hash $ value
where
leftPad bs = BS.replicate (25 - BS.length bs) 0 <> bs
 
-- utility
-- Convert from base58 encoded value to list of bytes
withError :: e -> Maybe a -> Either e a
toBytes :: Integer -> [Word8]
withError e = maybe (Left e) Right
toBytes x = reverse $ unfoldr (\b -> if b == 0 then Nothing else Just (fromIntegral $ b `mod` 256, b `div` 256)) x
 
-- Check validity of base58 encoded bitcoin address.
-- Result is either an error string (Left) or a validity boolunit (Right ()).
validityCheck :: String -> Either String Bool()
validityCheck encodedAddressencoded = do
num <- withError "Invalid base 58 encoding" $ decode58 encoded
let d58 = decode58 encodedAddress
let address = toBytes evnum
in case d58 of
when (BS.length address > 25) Nothing ->$ Left "InvalidAddress length baseexceeds 5825 encodingbytes"
when (BS.length address < 4) then$ Left "Address length less than 4 bytes"
Just ev ->
when (not $ checksumValid address) $ Left "Invalid checksum"
let address = toBytes ev
addressLength = length address
in if addressLength > 25
then Left "Address length exceeds 25 bytes"
else
if addressLength < 4
then Left "Address length less than 4 bytes"
else
let (bs,cs) = splitAt 21 $ replicate (25 - addressLength) 0 ++ address
in Right $ all (uncurry (==)) (zip cs $ unpack $ hash $ hash $ pack bs)
 
-- Run one validity check and display results.
validate :: String -> IO ()
validate encodedAddress = do
let vcresult = either show (const "Valid") $ validityCheck encodedAddress
putStrLn $ show encodedAddress ++ " -> " ++ show errresult
in case vc of
Left err ->
putStrLn $ show encodedAddress ++ " -> " ++ show err
Right validity ->
putStrLn $ show encodedAddress ++ " -> " ++ if validity then "Valid" else "Invalid"
 
-- Run some validity check tests.
Line 947 ⟶ 952:
validate "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- invalid chars
validate "1ANa55215ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- too long
validate "i55j" -- too short </lang>
</lang>
{{out}}
<pre style="font-size:80%">"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> Valid
Anonymous user