-- | Numbers in Bitcoin scripts {-# LANGUAGE PatternGuards #-} module Bitcoin.Script.Integer where -------------------------------------------------------------------------------- import Data.Int import Data.Word import Data.Bits import Data.List ( unfoldr , splitAt , mapAccumL ) import Data.Maybe import Control.Monad import Control.Applicative import Data.ByteString (ByteString) import qualified Data.ByteString as B import Bitcoin.Misc.BigInt import Bitcoin.Misc.OctetStream -------------------------------------------------------------------------------- -- * signs -- | Positive actually means non-negative here, but it looks better (and is easier to read) this way data Sign = Positive | Negative signOf :: Integer -> Sign signOf n = if n<0 then Negative else Positive toSignAbs :: Integer -> (Sign,Integer) toSignAbs n = (signOf n, abs n) fromSignAbs :: (Sign,Integer) -> Integer fromSignAbs (sign,absn) = case sign of Positive -> absn Negative -> negate absn -------------------------------------------------------------------------------- -- * Bitcoin's special ByteString <-> Integer conversion -- | Byte vectors are interpreted as little-endian variable-length integers -- with the most significant bit determining the sign of the integer. Thus -- 0x81 represents -1. 0x80 is another representation of zero (so called -- negative 0). Byte vectors are interpreted as Booleans where False is -- represented by any representation of zero, and True is represented by -- any representation of non-zero. asInteger :: B.ByteString -> Integer asInteger bs = case sign of Positive -> absn Negative -> negate absn where absn = littleEndianRollInteger ws (sign,ws) = decodeSign (B.unpack bs) asByteString :: Integer -> B.ByteString asByteString 0 = B.empty asByteString n = B.pack $ encodeSign (signOf n) $ littleEndianUnrollInteger (abs n) encodeSign :: Sign -> [Word8] -> [Word8] encodeSign sign = go where go ws = case ws of (x:rest@(y:_)) -> x : go rest [last] -> if last < 0x80 then case sign of { Positive -> [last] ; Negative -> [last + 0x80] } else last : go [] [] -> [ case sign of { Positive -> 0 ; Negative -> 0x80 } ] decodeSign :: [Word8] -> (Sign,[Word8]) decodeSign = go where go ws = case ws of (x:rest@(y:_)) -> let (sign,xs) = go rest in (sign,x:xs) [last] -> if last < 0x80 then (Positive, [last ] ) else (Negative, [last-0x80] ) [] -> ( Positive , [] ) --------------------------------------------------------------------------------