script

A Script library.
git clone git://git.ppad.tech/script.git
Log | Files | Refs | LICENSE

commit 552de904f73a82b3817a37175e6ffb19f4ca2bf5
parent 88c29318f0ed722577edb61f9c584556f5b01a0e
Author: Jared Tobin <jared@jtobin.io>
Date:   Mon, 16 Dec 2024 07:29:22 -0330

lib: misc op, script renderings/conversions

Diffstat:
Mlib/Bitcoin/Prim/Script.hs | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 201 insertions(+), 14 deletions(-)

diff --git a/lib/Bitcoin/Prim/Script.hs b/lib/Bitcoin/Prim/Script.hs @@ -1,15 +1,21 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE BinaryLiterals #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ViewPatterns #-} module Bitcoin.Prim.Script where +import Control.Monad (when) +import Control.Monad.ST import qualified Data.Bits as B -import Data.Bits ((.&.)) +import Data.Bits ((.&.), (.|.)) import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as BSB +import qualified Data.Char as C import qualified Data.Primitive.ByteArray as PB -import Data.Word (Word8) +import Data.STRef +import Data.Word (Word8, Word16, Word32) -- realization for small builders toStrict :: BSB.Builder -> BS.ByteString @@ -24,8 +30,8 @@ newtype Script = Script PB.ByteArray deriving (Eq, Show) -- | Render a 'Script' as a base16-encoded ByteString. -render :: Script -> BS.ByteString -render (Script bs) = toStrict (go 0) where +to_hex :: Script -> BS.ByteString +to_hex (Script bs) = toStrict (go 0) where l = PB.sizeofByteArray bs look = BS.index "0123456789abcdef" . fi @@ -39,20 +45,196 @@ render (Script bs) = toStrict (go 0) where in BSB.word8 w4_hi <> BSB.word8 w4_lo <> go (succ j) -to_script :: [Op] -> Script -to_script = Script . PB.byteArrayFromList . fmap op_to_byte +-- adapted from emilypi's 'base16' package +from_hex :: BS.ByteString -> Maybe Script +from_hex bs + | B.testBit l 0 = Nothing + | otherwise = runST $ do + arr <- PB.newByteArray k + ear <- newSTRef False -from_script :: Script -> [Op] -from_script (Script bs) = PB.foldrByteArray alg [] bs where - alg :: Word8 -> [Op] -> [Op] - alg b acc = (toEnum (fi b)) : acc + let loop i o + | i == l = pure () + | o == k = pure () + | otherwise = do + let x = BS.index bs i + y = BS.index bs (i + 1) -op_to_byte :: Op -> Word8 -op_to_byte = fi . fromEnum -{-# INLINE op_to_byte #-} + a = look hi x + b = look lo y + + when (a == 0xff) $ writeSTRef ear True + when (b == 0xff) $ writeSTRef ear True + + PB.writeByteArray arr o (a .|. b) + + loop (i + 2) (o + 1) + + loop 0 0 + + err <- readSTRef ear + if err + then pure Nothing + else do + ray <- PB.unsafeFreezeByteArray arr + pure (Just (Script ray)) + where + l = BS.length bs + k = l `quot` 2 + + look bet = BS.index bet . fi + + lo = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xff\xff\xff\xff\xff\xff\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + + hi = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\xff\xff\xff\xff\xff\xff\xff\xa0\xb0\xc0\xd0\xe0\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa0\xb0\xc0\xd0\xe0\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + +data Term = + OPCODE Opcode + | BYTE Word8 + deriving Eq + +instance Show Term where + show (OPCODE o) = show o + show (BYTE w) = + let look = BS.index "0123456789abcdef" . fi + w4_hi = look (w `B.shiftR` 4) + w4_lo = look (w .&. 0b00001111) + in "0x" <> (C.chr (fi w4_hi) : C.chr (fi w4_lo) : []) + +to_script :: [Term] -> Script +to_script = Script . PB.byteArrayFromList . fmap term_to_byte where + term_to_byte :: Term -> Word8 + term_to_byte = \case + OPCODE op -> fi (fromEnum op) + BYTE w8 -> fi w8 + {-# INLINE term_to_byte #-} + +from_script :: Script -> [Term] +from_script (Script bs) = go 0 where + l = PB.sizeofByteArray bs + + read_pay cur end + | cur > end = go cur + | otherwise = BYTE (PB.indexByteArray bs cur) : read_pay (cur + 1) end + + go j + | j == l = mempty + | otherwise = + let op = toEnum (fi (PB.indexByteArray bs j :: Word8)) :: Opcode + in case pushbytes op of + Just i -> OPCODE op : read_pay (j + 1) (j + 1 + i - 1) + Nothing -> OPCODE op : case op of + OP_PUSHDATA1 -> + let len_idx = j + 1 + pay_len = PB.indexByteArray bs len_idx :: Word8 + in BYTE pay_len + : read_pay (len_idx + 1) (len_idx + 1 + fi pay_len - 1) + + OP_PUSHDATA2 -> + let len_idx = j + 1 + w8_0 = PB.indexByteArray bs len_idx :: Word8 + w8_1 = PB.indexByteArray bs (len_idx + 1) :: Word8 + pay_len = fi w8_0 .|. fi w8_1 `B.shiftL` 8 :: Word16 + in BYTE w8_0 : BYTE w8_1 + : read_pay (len_idx + 2) (len_idx + 2 + fi pay_len - 1) + + OP_PUSHDATA4 -> + let len_idx = j + 1 + w8_0 = PB.indexByteArray bs len_idx :: Word8 + w8_1 = PB.indexByteArray bs (len_idx + 1) :: Word8 + w8_2 = PB.indexByteArray bs (len_idx + 2) :: Word8 + w8_3 = PB.indexByteArray bs (len_idx + 3) :: Word8 + pay_len = fi w8_0 + .|. fi w8_1 `B.shiftL` 8 + .|. fi w8_2 `B.shiftL` 16 + .|. fi w8_3 `B.shiftL` 24 :: Word32 + in BYTE w8_0 : BYTE w8_1 : BYTE w8_2 : BYTE w8_3 + : read_pay (len_idx + 4) (len_idx + 4 + fi pay_len - 1) + + _ -> go (succ j) + +pushbytes :: Opcode -> Maybe Int +pushbytes = \case + OP_PUSHBYTES_0 -> Just 00 + OP_PUSHBYTES_1 -> Just 01 + OP_PUSHBYTES_2 -> Just 02 + OP_PUSHBYTES_3 -> Just 03 + OP_PUSHBYTES_4 -> Just 04 + OP_PUSHBYTES_5 -> Just 05 + OP_PUSHBYTES_6 -> Just 06 + OP_PUSHBYTES_7 -> Just 07 + OP_PUSHBYTES_8 -> Just 08 + OP_PUSHBYTES_9 -> Just 09 + OP_PUSHBYTES_10 -> Just 10 + OP_PUSHBYTES_11 -> Just 11 + OP_PUSHBYTES_12 -> Just 12 + OP_PUSHBYTES_13 -> Just 13 + OP_PUSHBYTES_14 -> Just 14 + OP_PUSHBYTES_15 -> Just 15 + OP_PUSHBYTES_16 -> Just 16 + OP_PUSHBYTES_17 -> Just 17 + OP_PUSHBYTES_18 -> Just 18 + OP_PUSHBYTES_19 -> Just 19 + OP_PUSHBYTES_20 -> Just 20 + OP_PUSHBYTES_21 -> Just 21 + OP_PUSHBYTES_22 -> Just 22 + OP_PUSHBYTES_23 -> Just 23 + OP_PUSHBYTES_24 -> Just 24 + OP_PUSHBYTES_25 -> Just 25 + OP_PUSHBYTES_26 -> Just 26 + OP_PUSHBYTES_27 -> Just 27 + OP_PUSHBYTES_28 -> Just 28 + OP_PUSHBYTES_29 -> Just 29 + OP_PUSHBYTES_30 -> Just 30 + OP_PUSHBYTES_31 -> Just 31 + OP_PUSHBYTES_32 -> Just 32 + OP_PUSHBYTES_33 -> Just 33 + OP_PUSHBYTES_34 -> Just 34 + OP_PUSHBYTES_35 -> Just 35 + OP_PUSHBYTES_36 -> Just 36 + OP_PUSHBYTES_37 -> Just 37 + OP_PUSHBYTES_38 -> Just 38 + OP_PUSHBYTES_39 -> Just 39 + OP_PUSHBYTES_40 -> Just 40 + OP_PUSHBYTES_41 -> Just 41 + OP_PUSHBYTES_42 -> Just 42 + OP_PUSHBYTES_43 -> Just 43 + OP_PUSHBYTES_44 -> Just 44 + OP_PUSHBYTES_45 -> Just 45 + OP_PUSHBYTES_46 -> Just 46 + OP_PUSHBYTES_47 -> Just 47 + OP_PUSHBYTES_48 -> Just 48 + OP_PUSHBYTES_49 -> Just 49 + OP_PUSHBYTES_50 -> Just 50 + OP_PUSHBYTES_51 -> Just 51 + OP_PUSHBYTES_52 -> Just 52 + OP_PUSHBYTES_53 -> Just 53 + OP_PUSHBYTES_54 -> Just 54 + OP_PUSHBYTES_55 -> Just 55 + OP_PUSHBYTES_56 -> Just 56 + OP_PUSHBYTES_57 -> Just 57 + OP_PUSHBYTES_58 -> Just 58 + OP_PUSHBYTES_59 -> Just 59 + OP_PUSHBYTES_60 -> Just 60 + OP_PUSHBYTES_61 -> Just 61 + OP_PUSHBYTES_62 -> Just 62 + OP_PUSHBYTES_63 -> Just 63 + OP_PUSHBYTES_64 -> Just 64 + OP_PUSHBYTES_65 -> Just 65 + OP_PUSHBYTES_66 -> Just 66 + OP_PUSHBYTES_67 -> Just 67 + OP_PUSHBYTES_68 -> Just 68 + OP_PUSHBYTES_69 -> Just 69 + OP_PUSHBYTES_70 -> Just 70 + OP_PUSHBYTES_71 -> Just 71 + OP_PUSHBYTES_72 -> Just 72 + OP_PUSHBYTES_73 -> Just 73 + OP_PUSHBYTES_74 -> Just 74 + OP_PUSHBYTES_75 -> Just 75 + _ -> Nothing -- | Primitive opcodes. -data Op = +data Opcode = OP_PUSHBYTES_0 | OP_PUSHBYTES_1 | OP_PUSHBYTES_2 @@ -311,3 +493,8 @@ data Op = | OP_INVALIDOPCODE deriving (Eq, Show, Enum) +-- XX hacky test stuff + +test_s :: BS.ByteString +test_s = "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac" +