Bitcoin/address validation: Difference between revisions
Content added Content deleted
Line 895: | Line 895: | ||
</pre> |
</pre> |
||
=={{header|Haskell}}== |
=={{header|Haskell}}== |
||
<lang haskell>import |
<lang haskell>import Control.Monad (when) |
||
import Data. |
import Data.List (elemIndex) |
||
import |
import Data.Monoid ((<>)) |
||
import Data.ByteString |
import qualified Data.ByteString as BS |
||
import Data.ByteString (ByteString) |
|||
import Crypto.Hash.SHA256 (hash) -- from package cryptohash |
|||
-- Convert from base58 encoded value to Integer |
-- Convert from base58 encoded value to Integer |
||
decode58 :: String -> Maybe Integer |
decode58 :: String -> Maybe Integer |
||
decode58 = fmap combine . traverse parseDigit |
|||
decode58 = foldl (\v d -> (+) <$> ((58*) <$> v) <*> (fromIntegral <$> elemIndex d c58 )) $ Just 0 |
|||
where |
|||
⚫ | |||
combine = foldl (\acc digit -> 58 * acc + digit) 0 -- should be foldl', but this trips up the highlighting |
|||
parseDigit char = toInteger <$> elemIndex char c58 |
|||
⚫ | |||
⚫ | |||
⚫ | |||
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 |
|||
⚫ | |||
withError :: e -> Maybe a -> Either e a |
|||
⚫ | |||
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. |
-- Check validity of base58 encoded bitcoin address. |
||
-- Result is either an error string (Left) or |
-- Result is either an error string (Left) or unit (Right ()). |
||
validityCheck :: String -> Either String |
validityCheck :: String -> Either String () |
||
validityCheck |
validityCheck encoded = do |
||
num <- withError "Invalid base 58 encoding" $ decode58 encoded |
|||
let d58 = decode58 encodedAddress |
|||
⚫ | |||
in case d58 of |
|||
when (BS.length address > 25) $ Left "Address length exceeds 25 bytes" |
|||
⚫ | |||
Just ev -> |
|||
when (not $ checksumValid address) $ Left "Invalid checksum" |
|||
⚫ | |||
addressLength = length address |
|||
in if addressLength > 25 |
|||
then Left "Address length exceeds 25 bytes" |
|||
else |
|||
if addressLength < 4 |
|||
⚫ | |||
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. |
-- Run one validity check and display results. |
||
validate :: String -> IO () |
validate :: String -> IO () |
||
validate encodedAddress = |
validate encodedAddress = do |
||
let |
let result = either show (const "Valid") $ validityCheck encodedAddress |
||
⚫ | |||
in case vc of |
|||
Left err -> |
|||
⚫ | |||
Right validity -> |
|||
putStrLn $ show encodedAddress ++ " -> " ++ if validity then "Valid" else "Invalid" |
|||
-- Run some validity check tests. |
-- Run some validity check tests. |
||
Line 947: | Line 952: | ||
validate "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- invalid chars |
validate "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- invalid chars |
||
validate "1ANa55215ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- too long |
validate "1ANa55215ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- too long |
||
validate "i55j" -- too short |
validate "i55j" -- too short |
||
</lang> |
|||
{{out}} |
{{out}} |
||
<pre style="font-size:80%">"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> Valid |
<pre style="font-size:80%">"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> Valid |