haskoin-core 0.8.4 → 0.9.0
raw patch · 14 files changed
+110/−78 lines, 14 files
Files
- CHANGELOG.md +5/−0
- haskoin-core.cabal +8/−6
- src/Haskoin.hs +12/−9
- src/Network/Haskoin/Address.hs +16/−16
- src/Network/Haskoin/Block/Common.hs +7/−7
- src/Network/Haskoin/Keys/Extended.hs +1/−1
- src/Network/Haskoin/Transaction/Partial.hs +40/−14
- src/Network/Haskoin/Util.hs +1/−5
- test/Network/Haskoin/Address/CashAddrSpec.hs +12/−12
- test/Network/Haskoin/AddressSpec.hs +1/−1
- test/Network/Haskoin/CryptoSpec.hs +4/−4
- test/Network/Haskoin/Keys/ExtendedSpec.hs +1/−1
- test/Network/Haskoin/ScriptSpec.hs +1/−1
- test/Network/Haskoin/TransactionSpec.hs +1/−1
CHANGELOG.md view
@@ -4,9 +4,14 @@ 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). +## 0.9.0+### Changed+- Address conversion to string now defined for all inputs.+ ## 0.8.4 ### Added - Add reward computation to block functions.+- Add PSBT [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) types and functions ## 0.8.3 ### Added
haskoin-core.cabal view
@@ -1,25 +1,26 @@--- This file has been generated from package.yaml by hpack version 0.28.2.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.1. -- -- see: https://github.com/sol/hpack ----- hash: d2a9fa5a1034de67fddae041cd6e5f2473a5b97aa7951715ea23ef2c0154e909+-- hash: 88ebd4a51caa718564de69955c74a49d2279bc2d1fe883c83ae035f529168813 name: haskoin-core-version: 0.8.4+version: 0.9.0 synopsis: Bitcoin & Bitcoin Cash library for Haskell description: Haskoin Core is a complete Bitcoin and Bitcoin Cash library of functions and data types for Haskell developers. category: Bitcoin, Finance, Network homepage: http://github.com/haskoin/haskoin#readme bug-reports: http://github.com/haskoin/haskoin/issues author: Philippe Laprade,- Jean-Pierre Rupp+ Jean-Pierre Rupp,+ Matthew Wraith maintainer: xenog@protonmail.com license: PublicDomain license-file: UNLICENSE build-type: Simple-cabal-version: >= 1.10 extra-source-files:- CHANGELOG.md data/forkid_script_tests.json data/forkid_sighash.json data/script_tests.json@@ -27,6 +28,7 @@ data/sig_strict.json data/sighash.json README.md+ CHANGELOG.md source-repository head type: git
src/Haskoin.hs view
@@ -21,6 +21,8 @@ , module Block -- * Transactions , module Transaction+ -- * Partially Signed Bitcoin Transactions+ , module Partial -- * Scripts , module Script -- * Cryptographic Keys@@ -31,12 +33,13 @@ , module Util ) where -import Network.Haskoin.Address as Address-import Network.Haskoin.Block as Block-import Network.Haskoin.Constants as Constants-import Network.Haskoin.Crypto as Crypto-import Network.Haskoin.Keys as Keys-import Network.Haskoin.Network as Network-import Network.Haskoin.Script as Script-import Network.Haskoin.Transaction as Transaction-import Network.Haskoin.Util as Util+import Network.Haskoin.Address as Address+import Network.Haskoin.Block as Block+import Network.Haskoin.Constants as Constants+import Network.Haskoin.Crypto as Crypto+import Network.Haskoin.Keys as Keys+import Network.Haskoin.Network as Network+import Network.Haskoin.Script as Script+import Network.Haskoin.Transaction as Transaction+import Network.Haskoin.Transaction.Partial as Partial+import Network.Haskoin.Util as Util
src/Network/Haskoin/Address.hs view
@@ -35,6 +35,7 @@ , addressToOutput , payToScriptAddress , payToWitnessScriptAddress+ , payToNestedScriptAddress , scriptToAddress , scriptToAddressBS -- * Private Key Wallet Import Format (WIF)@@ -126,7 +127,7 @@ base58put _ _ = error "Cannot serialize this address as Base58" addrToJSON :: Network -> Address -> Value-addrToJSON net = String . addrToString net+addrToJSON net a = toJSON (addrToString net a) -- | JSON parsing for Bitcoin addresses. Works with 'Base58', 'CashAddr' and -- 'Bech32'.@@ -139,29 +140,23 @@ -- | Convert address to human-readable string. Uses 'Base58', 'Bech32', or -- 'CashAddr' depending on network.-addrToString :: Network -> Address -> Text+addrToString :: Network -> Address -> Maybe Text addrToString net a@PubKeyAddress {getAddrHash160 = h} | isNothing (getCashAddrPrefix net) =- encodeBase58Check $ runPut $ base58put net a+ Just . encodeBase58Check . runPut $ base58put net a | otherwise =- fromMaybe (error "Colud not encode a CashAddr") $ cashAddrEncode net 0 (S.encode h) addrToString net a@ScriptAddress {getAddrHash160 = h} | isNothing (getCashAddrPrefix net) =- encodeBase58Check $ runPut $ base58put net a+ Just . encodeBase58Check . runPut $ base58put net a | otherwise =- fromMaybe (error "Could not encode a CashAddr") $ cashAddrEncode net 1 (S.encode h)-addrToString net WitnessPubKeyAddress {getAddrHash160 = h} =- let mt = do- hrp <- getBech32Prefix net- segwitEncode hrp 0 (B.unpack (S.encode h))- in fromMaybe (error "Could not encode a Bech32 address") mt-addrToString net WitnessScriptAddress {getAddrHash256 = h} =- let mt = do- hrp <- getBech32Prefix net- segwitEncode hrp 0 (B.unpack (S.encode h))- in fromMaybe (error "Could not encode a Bech32 address") mt+addrToString net WitnessPubKeyAddress {getAddrHash160 = h} = do+ hrp <- getBech32Prefix net+ segwitEncode hrp 0 (B.unpack (S.encode h))+addrToString net WitnessScriptAddress {getAddrHash256 = h} = do+ hrp <- getBech32Prefix net+ segwitEncode hrp 0 (B.unpack (S.encode h)) -- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network. stringToAddr :: Network -> Text -> Maybe Address@@ -222,6 +217,11 @@ -- SegWit networks. payToWitnessScriptAddress :: ScriptOutput -> Address payToWitnessScriptAddress = p2wshAddr . sha256 . encodeOutputBS++-- | Compute a p2sh-p2wsh address, also known as a nested segwit address.+payToNestedScriptAddress :: ScriptOutput -> Address+payToNestedScriptAddress =+ p2shAddr . addressHash . encodeOutputBS . toP2WSH . encodeOutput -- | Encode an output script from an address. Will fail if using a -- pay-to-witness address on a non-SegWit network.
src/Network/Haskoin/Block/Common.hs view
@@ -121,19 +121,19 @@ -- 'Block'. Variations in the coinbase will result in different merkle roots in -- the 'BlockHeader'. data BlockHeader =- BlockHeader { blockVersion :: !Word32 -- 16 bytes+ BlockHeader { blockVersion :: !Word32 -- 4 bytes -- | hash of the previous block (parent)- , prevBlock :: !BlockHash -- 64 bytes+ , prevBlock :: !BlockHash -- 32 bytes -- | root of the merkle tree of transactions- , merkleRoot :: !Hash256 -- 64 bytes+ , merkleRoot :: !Hash256 -- 32 bytes -- | unix timestamp- , blockTimestamp :: !Timestamp -- 16 bytes+ , blockTimestamp :: !Timestamp -- 4 bytes -- | difficulty target- , blockBits :: !Word32 -- 16 bytes+ , blockBits :: !Word32 -- 4 bytes -- | random nonce- , bhNonce :: !Word32 -- 16 bytes+ , bhNonce :: !Word32 -- 4 bytes } deriving (Eq, Ord, Show, Read, Generic, Hashable)- -- 208 bytes (above + 16 bytes)+ -- 80 bytes -- | Compute hash of 'BlockHeader'. headerHash :: BlockHeader -> BlockHash
src/Network/Haskoin/Keys/Extended.hs view
@@ -532,7 +532,7 @@ Deriv -> Just Deriv _ -> Nothing --- | Turn a derivatino path into a soft derivation path. Will fail if the path+-- | Turn a derivation path into a soft derivation path. Will fail if the path -- has hard derivations. toSoft :: DerivPathI t -> Maybe SoftPath toSoft p = case p of
src/Network/Haskoin/Transaction/Partial.hs view
@@ -3,7 +3,16 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}+{-|+Module : Network.Haskoin.Transaction.Partial+Copyright : No rights reserved+License : UNLICENSE+Maintainer : matt@bitnomial.com+Stability : experimental+Portability : POSIX +Code related to PSBT parsing and serialization.+-} module Network.Haskoin.Transaction.Partial ( PartiallySignedTransaction (..) , Input (..)@@ -29,26 +38,27 @@ import qualified Data.HashMap.Strict as HashMap import Data.List (foldl') import Data.Maybe (fromMaybe, isJust)-import Data.Proxy (Proxy) import Data.Serialize as S import GHC.Generics (Generic)-import GHC.Word (Word64, Word8)+import GHC.Word (Word32, Word8) import Network.Haskoin.Address (Address (..), pubKeyAddr) import Network.Haskoin.Keys (Fingerprint, KeyIndex, PubKeyI) import Network.Haskoin.Network (VarInt (..), VarString (..), putVarInt)-import Network.Haskoin.Script (Script (..), ScriptInput (..),- ScriptOp (..), ScriptOutput (..),- SigHash, SimpleInput (..),+import Network.Haskoin.Script (Script (..), ScriptOp (..),+ ScriptOutput (..), SigHash, decodeOutput, decodeOutputBS,- encodeInput, encodeOutputBS,- isPayScriptHash, opPushData,- toP2SH, toP2WSH)+ encodeOutputBS, isPayScriptHash,+ opPushData, toP2SH, toP2WSH) import Network.Haskoin.Transaction (Tx (..), TxOut, WitnessStack, outPointIndex, prevOutput, scriptInput, scriptOutput) import Network.Haskoin.Util (eitherToMaybe) +-- | PSBT data type as specified in [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki). This+-- contains an unsigned transaction, inputs and outputs, and unspecified extra data. There is one input per input in the+-- unsigned transaction, and one output per output in the unsigned transaction. The inputs and outputs in the+-- 'PartiallySignedTransaction' line up by index with the inputs and outputs in the unsigned transaction. data PartiallySignedTransaction = PartiallySignedTransaction { unsignedTransaction :: Tx , globalUnknown :: UnknownMap@@ -56,6 +66,7 @@ , outputs :: [Output] } deriving (Show, Eq) +-- | Inputs contain all of the data needed to sign a transaction and all of the resulting signature data after signing. data Input = Input { nonWitnessUtxo :: Maybe Tx , witnessUtxo :: Maybe TxOut@@ -69,6 +80,7 @@ , inputUnknown :: UnknownMap } deriving (Show, Eq) +-- | Outputs can contain information needed to spend the output at a later date. data Output = Output { outputRedeemScript :: Maybe Script , outputWitnessScript :: Maybe Script@@ -76,9 +88,12 @@ , outputUnknown :: UnknownMap } deriving (Show, Eq) +-- | A map of raw PSBT keys to byte strings for extra data. The 'keyType' field cannot overlap with any of the reserved+-- 'keyType' fields specified in the PSBT specification. newtype UnknownMap = UnknownMap { unknownMap :: HashMap Key ByteString } deriving (Show, Eq, Semigroup, Monoid) +-- | Raw keys for the map type used in PSBTs. data Key = Key { keyType :: Word8 , key :: ByteString@@ -86,6 +101,7 @@ instance Hashable Key +-- | Take two 'PartiallySignedTransaction's and merge them. The 'unsignedTransaction' field in both must be the same. merge :: PartiallySignedTransaction -> PartiallySignedTransaction -> Maybe PartiallySignedTransaction merge psbt1 psbt2 | unsignedTransaction psbt1 == unsignedTransaction psbt2@@ -120,8 +136,9 @@ , outputUnknown = outputUnknown a <> outputUnknown b } +-- | Take partial signatures from all of the 'Input's and finalize the signature. complete :: PartiallySignedTransaction -> PartiallySignedTransaction-complete psbt = psbt { inputs = map (completeInput . analyzeInputs) (zip [0..] $ inputs psbt) }+complete psbt = psbt { inputs = map (completeInput . analyzeInputs) (indexed $ inputs psbt) } where analyzeInputs (i, input) = (outputScript =<< witnessUtxo input <|> nonWitScript, input) where@@ -129,13 +146,16 @@ getPrevOut i tx = (txOut tx !!?) . fromIntegral . outPointIndex . prevOutput =<< txIn (unsignedTransaction psbt) !!? i- xs !!? i = lookup i $ zip [0..] xs+ xs !!? i = lookup i $ indexed xs outputScript = eitherToMaybe . decodeOutputBS . scriptOutput completeInput (Nothing, input) = input completeInput (Just script, input) = completeSig input script + indexed :: [a] -> [(Word32, a)]+ indexed = zip [0..]+ completeSig :: Input -> ScriptOutput -> Input completeSig input (PayPK k) = input { finalScriptSig = eitherToMaybe . S.decode =<< HashMap.lookup k (partialSigs input) }@@ -182,6 +202,8 @@ where lookupKey sigs key = maybe sigs (:sigs) $ HashMap.lookup key (partialSigs input) +-- | Take a finalized 'PartiallySignedTransaction' and produce the signed final transaction. You may need to call+-- 'complete' on the 'PartiallySignedTransaction' before producing the final transaction. finalTransaction :: PartiallySignedTransaction -> Tx finalTransaction psbt = setInputs . foldl' finalizeInput ([], []) $ zip (txIn tx) (inputs psbt) where@@ -193,6 +215,7 @@ finalScript script = (txInput { scriptInput = encode script }:ins, []:witData) finalWitness = (ins, fromMaybe [] (finalScriptWitness psbtInput):witData) +-- | Take an unsigned transaction and produce an empty 'PartiallySignedTransaction' emptyPSBT :: Tx -> PartiallySignedTransaction emptyPSBT tx = PartiallySignedTransaction { unsignedTransaction = tx@@ -298,7 +321,7 @@ where putPartialSig = putPubKeyMap InPartialSig . fmap VarString putSigHash sigHash = do- putVarInt 1 >> putWord8 (enumWord8 InSigHashType)+ putKey InSigHashType putWord8 0x04 putWord32le (fromIntegral sigHash) @@ -322,8 +345,11 @@ getSizedBytes = getNested (fromIntegral . getVarInt <$> get) get putKeyValue :: (Enum t, Serialize v) => t -> v -> Put-putKeyValue t v = putVarInt 1 >> putWord8 (enumWord8 t) >> putSizedBytes v+putKeyValue t v = putKey t >> putSizedBytes v +putKey :: Enum t => t -> Put+putKey t = putVarInt (1 :: Word8) >> putWord8 (enumWord8 t)+ getMap :: (Bounded t, Enum t) => (Int -> a -> t -> Get a) -> ((HashMap Key ByteString -> HashMap Key ByteString) -> a -> a)@@ -367,10 +393,10 @@ utxo <- getSizedBytes return $ input { witnessUtxo = Just utxo } getInputItem keySize input InPartialSig = do- (k, v) <- getPartialSig keySize+ (k, v) <- getPartialSig return $ input { partialSigs = HashMap.insert k v (partialSigs input) } where- getPartialSig keySize = (,) <$> isolate keySize get <*> (getVarString <$> get)+ getPartialSig = (,) <$> isolate keySize get <*> (getVarString <$> get) getInputItem 0 input@Input{sigHashType = Nothing} InSigHashType = do VarInt size <- get guard $ size == 0x04
src/Network/Haskoin/Util.hs view
@@ -41,7 +41,7 @@ ) where import Control.Monad (guard)-import Control.Monad.Except (ExceptT (..))+import Control.Monad.Except (ExceptT (..), liftEither) import Data.Aeson.Types (Options (..), SumEncoding (..), defaultOptions, defaultTaggedObject) import Data.Bits@@ -107,10 +107,6 @@ -- 'Right' and 'Nothing' is mapped to 'Left'. Default 'Left' required. maybeToEither :: b -> Maybe a -> Either b a maybeToEither err = maybe (Left err) Right---- | Lift a 'Either' computation into the 'ExceptT' monad.-liftEither :: Monad m => Either b a -> ExceptT b m a-liftEither = ExceptT . return -- | Lift a 'Maybe' computation into the 'ExceptT' monad. liftMaybe :: Monad m => b -> Maybe a -> ExceptT b m a
test/Network/Haskoin/Address/CashAddrSpec.hs view
@@ -44,79 +44,79 @@ describe "cashaddr to base58 translation test vectors" $ do it "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" $ do let addr =- addrToString bch <$>+ addrToString bch =<< stringToAddr btc "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" addr `shouldBe` Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" it "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR" $ do let addr =- addrToString bch <$>+ addrToString bch =<< stringToAddr btc "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR" addr `shouldBe` Just "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy" it "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb" $ do let addr =- addrToString bch <$>+ addrToString bch =<< stringToAddr btc "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb" addr `shouldBe` Just "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r" it "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC" $ do let addr =- addrToString bch <$>+ addrToString bch =<< stringToAddr btc "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC" addr `shouldBe` Just "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq" it "3LDsS579y7sruadqu11beEJoTjdFiFCdX4" $ do let addr =- addrToString bch <$>+ addrToString bch =<< stringToAddr btc "3LDsS579y7sruadqu11beEJoTjdFiFCdX4" addr `shouldBe` Just "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e" it "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw" $ do let addr =- addrToString bch <$>+ addrToString bch =<< stringToAddr btc "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw" addr `shouldBe` Just "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37" describe "base58 to cashaddr translation test vectors" $ do it "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" $ do let addr =- addrToString btc <$>+ addrToString btc =<< stringToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" addr `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" it "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy" $ do let addr =- addrToString btc <$>+ addrToString btc =<< stringToAddr bch "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy" addr `shouldBe` Just "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR" it "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r" $ do let addr =- addrToString btc <$>+ addrToString btc =<< stringToAddr bch "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r" addr `shouldBe` Just "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb" it "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq" $ do let addr =- addrToString btc <$>+ addrToString btc =<< stringToAddr bch "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq" addr `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC" it "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e" $ do let addr =- addrToString btc <$>+ addrToString btc =<< stringToAddr bch "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e" addr `shouldBe` Just "3LDsS579y7sruadqu11beEJoTjdFiFCdX4" it "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37" $ do let addr =- addrToString btc <$>+ addrToString btc =<< stringToAddr bch "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"
test/Network/Haskoin/AddressSpec.hs view
@@ -39,7 +39,7 @@ it "encodes and decodes address" $ property $ forAll arbitraryAddress $ \a ->- stringToAddr net (addrToString net a) == Just a+ (stringToAddr net =<< addrToString net a) == Just a it "shows and reads address" $ property $ forAll arbitraryAddress $ \a -> read (show a) == a
test/Network/Haskoin/CryptoSpec.hs view
@@ -136,10 +136,10 @@ checkMatchingAddress :: Assertion checkMatchingAddress = do- assertBool "Key 1" $ addr1 == addrToString btc (pubKeyAddr pub1)- assertBool "Key 2" $ addr2 == addrToString btc (pubKeyAddr pub2)- assertBool "Key 1C" $ addr1C == addrToString btc (pubKeyAddr pub1C)- assertBool "Key 2C" $ addr2C == addrToString btc (pubKeyAddr pub2C)+ assertBool "Key 1" $ Just addr1 == addrToString btc (pubKeyAddr pub1)+ assertBool "Key 2" $ Just addr2 == addrToString btc (pubKeyAddr pub2)+ assertBool "Key 1C" $ Just addr1C == addrToString btc (pubKeyAddr pub1C)+ assertBool "Key 2C" $ Just addr2C == addrToString btc (pubKeyAddr pub2C) checkSignatures :: Hash256 -> Assertion checkSignatures h = do
test/Network/Haskoin/Keys/ExtendedSpec.hs view
@@ -293,7 +293,7 @@ assertBool "xPrvID" $ encodeHex (S.encode $ xPrvID m) == head v assertBool "xPrvFP" $ encodeHex (S.encode $ xPrvFP m) == v !! 1 assertBool "xPrvAddr" $- addrToString btc (xPubAddr $ deriveXPubKey m) == v !! 2+ addrToString btc (xPubAddr $ deriveXPubKey m) == Just (v !! 2) assertBool "prvKey" $ encodeHex (getSecKey $ xPrvKey m) == v !! 3 assertBool "xPrvWIF" $ xPrvWif btc m == v !! 4 assertBool "pubKey" $
test/Network/Haskoin/ScriptSpec.hs view
@@ -364,7 +364,7 @@ b = do o <- s d <- eitherToMaybe $ decodeOutput o- return . addrToString btc $ payToScriptAddress d+ addrToString btc $ payToScriptAddress d sigDecodeMap :: Network -> (Text, Int) -> Spec sigDecodeMap net (_, i) =
test/Network/Haskoin/TransactionSpec.hs view
@@ -170,7 +170,7 @@ | isScriptAddress a = Right (PayScriptHash (getAddrHash160 a)) == out | otherwise = undefined where- tx = buildAddrTx net [] [(addrToString net a, v)]+ tx = buildAddrTx net [] [(fromJust (addrToString net a), v)] out = decodeOutputBS $ scriptOutput $