packages feed

haskoin-core 1.4.0 → 1.5.0

raw patch · 65 files changed

+13701/−4259 lines, 65 filesdep +unicode-transformsdep ~base64dep ~secp256k1-haskellPVP ok

version bump matches the API change (PVP)

Dependencies added: unicode-transforms

Dependency ranges changed: base64, secp256k1-haskell

API changes (from Hackage documentation)

- Haskoin.Address: CashAddress :: !Word8 -> !ByteString -> Address
- Haskoin.Address: WitnessAddress :: !Word8 -> !ByteString -> Address
- Haskoin.Block.Merkle: traverseAndBuild :: Int -> Int -> [(TxHash, Bool)] -> (FlagBits, PartialMerkleTree)
- Haskoin.Block.Merkle: traverseAndExtract :: Int -> Int -> Int -> FlagBits -> PartialMerkleTree -> Maybe (MerkleRoot, [TxHash], Int, Int)
- Haskoin.Crypto.Keys.Common: instance GHC.Internal.Read.Read Haskoin.Crypto.Keys.Common.PrivateKey
- Haskoin.Crypto.Keys.Extended: instance GHC.Internal.Read.Read Haskoin.Crypto.Keys.Extended.XKey
- Haskoin.Crypto.Keys.Extended: instance GHC.Internal.Read.Read Haskoin.Crypto.Keys.Extended.XPrvKey
- Haskoin.Network.Data: [bip34Block] :: Network -> !(BlockHeight, BlockHash)
- Haskoin.Transaction.Builder: chooseMSCoins :: Coin c => Word64 -> Word64 -> (Int, Int) -> Int -> Bool -> [c] -> Either String ([c], Word64)
- Haskoin.Transaction.Builder: chooseMSCoinsSink :: forall (m :: Type -> Type) c. (Monad m, Coin c) => Word64 -> Word64 -> (Int, Int) -> Int -> Bool -> ConduitT c Void m (Either String ([c], Word64))
- Haskoin.Transaction.Builder: guessMSSize :: (Int, Int) -> Int
- Haskoin.Transaction.Builder: guessMSTxFee :: Word64 -> (Int, Int) -> Int -> Int -> Word64
- Haskoin.Transaction.Genesis: genesisTx :: Ctx -> Tx
- Haskoin.Transaction.Partial: Input :: Maybe Tx -> Maybe TxOut -> HashMap PublicKey ByteString -> Maybe SigHash -> Maybe Script -> Maybe Script -> HashMap PublicKey (Fingerprint, [KeyIndex]) -> Maybe Script -> Maybe WitnessStack -> UnknownMap -> Input
- Haskoin.Transaction.Partial: Key :: Word8 -> ByteString -> Key
- Haskoin.Transaction.Partial: Output :: Maybe Script -> Maybe Script -> HashMap PublicKey (Fingerprint, [KeyIndex]) -> UnknownMap -> Output
- Haskoin.Transaction.Partial: PSBT :: Tx -> UnknownMap -> [Input] -> [Output] -> PSBT
- Haskoin.Transaction.Partial: UnknownMap :: HashMap Key ByteString -> UnknownMap
- Haskoin.Transaction.Partial: [finalScriptSig] :: Input -> Maybe Script
- Haskoin.Transaction.Partial: [finalScriptWitness] :: Input -> Maybe WitnessStack
- Haskoin.Transaction.Partial: [globalUnknown] :: PSBT -> UnknownMap
- Haskoin.Transaction.Partial: [inputHDKeypaths] :: Input -> HashMap PublicKey (Fingerprint, [KeyIndex])
- Haskoin.Transaction.Partial: [inputRedeemScript] :: Input -> Maybe Script
- Haskoin.Transaction.Partial: [inputUnknown] :: Input -> UnknownMap
- Haskoin.Transaction.Partial: [inputWitnessScript] :: Input -> Maybe Script
- Haskoin.Transaction.Partial: [inputs] :: PSBT -> [Input]
- Haskoin.Transaction.Partial: [keyType] :: Key -> Word8
- Haskoin.Transaction.Partial: [key] :: Key -> ByteString
- Haskoin.Transaction.Partial: [nonWitnessUtxo] :: Input -> Maybe Tx
- Haskoin.Transaction.Partial: [outputHDKeypaths] :: Output -> HashMap PublicKey (Fingerprint, [KeyIndex])
- Haskoin.Transaction.Partial: [outputRedeemScript] :: Output -> Maybe Script
- Haskoin.Transaction.Partial: [outputUnknown] :: Output -> UnknownMap
- Haskoin.Transaction.Partial: [outputWitnessScript] :: Output -> Maybe Script
- Haskoin.Transaction.Partial: [outputs] :: PSBT -> [Output]
- Haskoin.Transaction.Partial: [partialSigs] :: Input -> HashMap PublicKey ByteString
- Haskoin.Transaction.Partial: [sigHashType] :: Input -> Maybe SigHash
- Haskoin.Transaction.Partial: [unknownMap] :: UnknownMap -> HashMap Key ByteString
- Haskoin.Transaction.Partial: [unsignedTransaction] :: PSBT -> Tx
- Haskoin.Transaction.Partial: [witnessUtxo] :: Input -> Maybe TxOut
- Haskoin.Transaction.Partial: complete :: Ctx -> PSBT -> PSBT
- Haskoin.Transaction.Partial: data Input
- Haskoin.Transaction.Partial: data Key
- Haskoin.Transaction.Partial: data Output
- Haskoin.Transaction.Partial: data PSBT
- Haskoin.Transaction.Partial: data PSBTSigner
- Haskoin.Transaction.Partial: emptyInput :: Input
- Haskoin.Transaction.Partial: emptyOutput :: Output
- Haskoin.Transaction.Partial: emptyPSBT :: Tx -> PSBT
- Haskoin.Transaction.Partial: finalTransaction :: PSBT -> Tx
- Haskoin.Transaction.Partial: getInput :: MonadGet m => Ctx -> m Input
- Haskoin.Transaction.Partial: getOutput :: MonadGet m => Ctx -> m Output
- Haskoin.Transaction.Partial: getPSBT :: MonadGet m => Ctx -> m PSBT
- Haskoin.Transaction.Partial: getSignerKey :: PSBTSigner -> PublicKey -> Maybe (Fingerprint, DerivPath) -> Maybe SecKey
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.Input
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.InputType
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.Output
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.OutputType
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.PSBT
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance Control.DeepSeq.NFData Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance Data.Binary.Class.Binary Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance Data.Binary.Class.Binary Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance Data.Bytes.Serial.Serial Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance Data.Bytes.Serial.Serial Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance Data.Bytes.Serial.Serial Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance Data.Hashable.Class.Hashable Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance Data.Serialize.Serialize Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance Data.Serialize.Serialize Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.Input
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.InputType
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.Output
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.OutputType
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.PSBT
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance GHC.Classes.Eq Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance GHC.Internal.Base.Monoid Haskoin.Transaction.Partial.PSBTSigner
- Haskoin.Transaction.Partial: instance GHC.Internal.Base.Monoid Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance GHC.Internal.Base.Semigroup Haskoin.Transaction.Partial.PSBTSigner
- Haskoin.Transaction.Partial: instance GHC.Internal.Base.Semigroup Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance GHC.Internal.Enum.Bounded Haskoin.Transaction.Partial.InputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Enum.Bounded Haskoin.Transaction.Partial.OutputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Enum.Enum Haskoin.Transaction.Partial.InputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Enum.Enum Haskoin.Transaction.Partial.OutputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.Input
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.InputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.Output
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.OutputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.PSBT
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance GHC.Internal.Read.Read Haskoin.Transaction.Partial.Input
- Haskoin.Transaction.Partial: instance GHC.Internal.Read.Read Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance GHC.Internal.Read.Read Haskoin.Transaction.Partial.Output
- Haskoin.Transaction.Partial: instance GHC.Internal.Read.Read Haskoin.Transaction.Partial.PSBT
- Haskoin.Transaction.Partial: instance GHC.Internal.Read.Read Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.Input
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.InputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.Key
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.Output
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.OutputType
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.PSBT
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.PSBTHDPath
- Haskoin.Transaction.Partial: instance GHC.Internal.Show.Show Haskoin.Transaction.Partial.UnknownMap
- Haskoin.Transaction.Partial: merge :: PSBT -> PSBT -> Maybe PSBT
- Haskoin.Transaction.Partial: mergeInput :: Input -> Input -> Input
- Haskoin.Transaction.Partial: mergeMany :: [PSBT] -> Maybe PSBT
- Haskoin.Transaction.Partial: mergeOutput :: Output -> Output -> Output
- Haskoin.Transaction.Partial: newtype UnknownMap
- Haskoin.Transaction.Partial: putInput :: MonadPut m => Ctx -> Input -> m ()
- Haskoin.Transaction.Partial: putOutput :: MonadPut m => Ctx -> Output -> m ()
- Haskoin.Transaction.Partial: putPSBT :: MonadPut m => Ctx -> PSBT -> m ()
- Haskoin.Transaction.Partial: secKeySigner :: Ctx -> SecKey -> PSBTSigner
- Haskoin.Transaction.Partial: signPSBT :: Network -> Ctx -> PSBTSigner -> PSBT -> PSBT
- Haskoin.Transaction.Partial: xPrvSigner :: Ctx -> XPrvKey -> Maybe (Fingerprint, DerivPath) -> PSBTSigner
- Haskoin.Util.Arbitrary.Transaction: newtype TestCoin
+ Haskoin.Address: addrToBase58 :: Network -> Address -> Maybe Base58
+ Haskoin.Address: addrToBech32 :: Network -> Address -> Maybe Bech32
+ Haskoin.Address: addrToCash :: Network -> Address -> Maybe CashAddr
+ Haskoin.Address: pattern CashAddress :: Word8 -> ByteString -> Address
+ Haskoin.Address: pattern WitnessAddress :: Word8 -> ByteString -> Address
+ Haskoin.Block.Headers: asertAnchor :: BlockHeaders m => Network -> BlockNode -> m (Maybe BlockNode)
+ Haskoin.Block.Headers: medianTime :: [Timestamp] -> Timestamp
+ Haskoin.Block.Headers: skipHeight :: BlockHeight -> BlockHeight
+ Haskoin.Crypto.Keys.Common: decodePubKey :: Ctx -> ByteString -> Either String PublicKey
+ Haskoin.Crypto.Keys.Common: decodeSecKey :: ByteString -> Maybe SecKey
+ Haskoin.Crypto.Signature: hashToMsg :: Hash256 -> Msg
+ Haskoin.Network.BlockFilter: CFCheckpt :: !FilterType -> !BlockHash -> ![Hash256] -> CFCheckpt
+ Haskoin.Network.BlockFilter: CFHeaders :: !FilterType -> !BlockHash -> !Hash256 -> ![Hash256] -> CFHeaders
+ Haskoin.Network.BlockFilter: CFilter :: !FilterType -> !BlockHash -> !ByteString -> CFilter
+ Haskoin.Network.BlockFilter: GetCFCheckpt :: !FilterType -> !BlockHash -> GetCFCheckpt
+ Haskoin.Network.BlockFilter: GetCFHeaders :: !FilterType -> !Word32 -> !BlockHash -> GetCFHeaders
+ Haskoin.Network.BlockFilter: GetCFilters :: !FilterType -> !Word32 -> !BlockHash -> GetCFilters
+ Haskoin.Network.BlockFilter: [blockHash] :: CFilter -> !BlockHash
+ Haskoin.Network.BlockFilter: [filterBytes] :: CFilter -> !ByteString
+ Haskoin.Network.BlockFilter: [filterHashes] :: CFHeaders -> ![Hash256]
+ Haskoin.Network.BlockFilter: [filterHeaders] :: CFCheckpt -> ![Hash256]
+ Haskoin.Network.BlockFilter: [filterType] :: CFCheckpt -> !FilterType
+ Haskoin.Network.BlockFilter: [previousHeader] :: CFHeaders -> !Hash256
+ Haskoin.Network.BlockFilter: [startHeight] :: GetCFHeaders -> !Word32
+ Haskoin.Network.BlockFilter: [stopHash] :: CFCheckpt -> !BlockHash
+ Haskoin.Network.BlockFilter: basicFilterType :: FilterType
+ Haskoin.Network.BlockFilter: data CFCheckpt
+ Haskoin.Network.BlockFilter: data CFHeaders
+ Haskoin.Network.BlockFilter: data CFilter
+ Haskoin.Network.BlockFilter: data GetCFCheckpt
+ Haskoin.Network.BlockFilter: data GetCFHeaders
+ Haskoin.Network.BlockFilter: data GetCFilters
+ Haskoin.Network.BlockFilter: instance Control.DeepSeq.NFData Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance Control.DeepSeq.NFData Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance Control.DeepSeq.NFData Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance Control.DeepSeq.NFData Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance Control.DeepSeq.NFData Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance Control.DeepSeq.NFData Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: instance Data.Binary.Class.Binary Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance Data.Binary.Class.Binary Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance Data.Binary.Class.Binary Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance Data.Binary.Class.Binary Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance Data.Binary.Class.Binary Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance Data.Binary.Class.Binary Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: instance Data.Bytes.Serial.Serial Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance Data.Bytes.Serial.Serial Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance Data.Bytes.Serial.Serial Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance Data.Bytes.Serial.Serial Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance Data.Bytes.Serial.Serial Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance Data.Bytes.Serial.Serial Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: instance Data.Serialize.Serialize Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance Data.Serialize.Serialize Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance Data.Serialize.Serialize Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance Data.Serialize.Serialize Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance Data.Serialize.Serialize Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance Data.Serialize.Serialize Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: instance GHC.Classes.Eq Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance GHC.Classes.Eq Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance GHC.Classes.Eq Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance GHC.Classes.Eq Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance GHC.Classes.Eq Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance GHC.Classes.Eq Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Generics.Generic Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Generics.Generic Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Generics.Generic Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Generics.Generic Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Generics.Generic Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Generics.Generic Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Show.Show Haskoin.Network.BlockFilter.CFCheckpt
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Show.Show Haskoin.Network.BlockFilter.CFHeaders
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Show.Show Haskoin.Network.BlockFilter.CFilter
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Show.Show Haskoin.Network.BlockFilter.GetCFCheckpt
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Show.Show Haskoin.Network.BlockFilter.GetCFHeaders
+ Haskoin.Network.BlockFilter: instance GHC.Internal.Show.Show Haskoin.Network.BlockFilter.GetCFilters
+ Haskoin.Network.BlockFilter: type FilterType = Word8
+ Haskoin.Network.Bloom: maxBloomSize :: Int
+ Haskoin.Network.Bloom: maxFilterAddSize :: Int
+ Haskoin.Network.Bloom: maxHashFuncs :: Word32
+ Haskoin.Network.Bloom: scriptPushData :: ByteString -> [ByteString]
+ Haskoin.Network.Common: AddrCJDNS :: AddressNetwork
+ Haskoin.Network.Common: AddrI2P :: AddressNetwork
+ Haskoin.Network.Common: AddrIPv4 :: AddressNetwork
+ Haskoin.Network.Common: AddrIPv6 :: AddressNetwork
+ Haskoin.Network.Common: AddrTorV2 :: AddressNetwork
+ Haskoin.Network.Common: AddrTorV3 :: AddressNetwork
+ Haskoin.Network.Common: AddrUnknown :: Word8 -> AddressNetwork
+ Haskoin.Network.Common: AddrV2 :: [NetworkAddressV2] -> AddrV2
+ Haskoin.Network.Common: AddrYggdrasil :: AddressNetwork
+ Haskoin.Network.Common: FeeFilter :: Word64 -> FeeFilter
+ Haskoin.Network.Common: InvCompactBlock :: InvType
+ Haskoin.Network.Common: InvWTx :: InvType
+ Haskoin.Network.Common: MCAddrV2 :: MessageCommand
+ Haskoin.Network.Common: MCBlockTxn :: MessageCommand
+ Haskoin.Network.Common: MCCFCheckpt :: MessageCommand
+ Haskoin.Network.Common: MCCFHeaders :: MessageCommand
+ Haskoin.Network.Common: MCCFilter :: MessageCommand
+ Haskoin.Network.Common: MCCmpctBlock :: MessageCommand
+ Haskoin.Network.Common: MCFeeFilter :: MessageCommand
+ Haskoin.Network.Common: MCGetBlockTxn :: MessageCommand
+ Haskoin.Network.Common: MCGetCFCheckpt :: MessageCommand
+ Haskoin.Network.Common: MCGetCFHeaders :: MessageCommand
+ Haskoin.Network.Common: MCGetCFilters :: MessageCommand
+ Haskoin.Network.Common: MCSendAddrV2 :: MessageCommand
+ Haskoin.Network.Common: MCSendCmpct :: MessageCommand
+ Haskoin.Network.Common: MCSendTxRcncl :: MessageCommand
+ Haskoin.Network.Common: MCWtxidRelay :: MessageCommand
+ Haskoin.Network.Common: NetworkAddressV2 :: !Word32 -> !Word64 -> !AddressNetwork -> !ByteString -> !Word16 -> NetworkAddressV2
+ Haskoin.Network.Common: SendTxRcncl :: !Word32 -> !Word64 -> SendTxRcncl
+ Haskoin.Network.Common: [feeRate] :: FeeFilter -> Word64
+ Haskoin.Network.Common: [network] :: NetworkAddressV2 -> !AddressNetwork
+ Haskoin.Network.Common: [port] :: NetworkAddressV2 -> !Word16
+ Haskoin.Network.Common: [salt] :: SendTxRcncl -> !Word64
+ Haskoin.Network.Common: [time] :: NetworkAddressV2 -> !Word32
+ Haskoin.Network.Common: addressNetworkId :: AddressNetwork -> Word8
+ Haskoin.Network.Common: addressNetworkLength :: AddressNetwork -> Maybe Int
+ Haskoin.Network.Common: data AddressNetwork
+ Haskoin.Network.Common: data NetworkAddressV2
+ Haskoin.Network.Common: data SendTxRcncl
+ Haskoin.Network.Common: fromNetworkAddressV2 :: NetworkAddressV2 -> Maybe NetworkAddressTime
+ Haskoin.Network.Common: getVarIntBytes :: MonadGet m => m ByteString
+ Haskoin.Network.Common: getVarIntList :: MonadGet m => m a -> m [a]
+ Haskoin.Network.Common: instance Control.DeepSeq.NFData Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance Control.DeepSeq.NFData Haskoin.Network.Common.AddressNetwork
+ Haskoin.Network.Common: instance Control.DeepSeq.NFData Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance Control.DeepSeq.NFData Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance Control.DeepSeq.NFData Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance Data.Binary.Class.Binary Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance Data.Binary.Class.Binary Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance Data.Binary.Class.Binary Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance Data.Binary.Class.Binary Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance Data.Bytes.Serial.Serial Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance Data.Bytes.Serial.Serial Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance Data.Bytes.Serial.Serial Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance Data.Bytes.Serial.Serial Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance Data.Serialize.Serialize Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance Data.Serialize.Serialize Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance Data.Serialize.Serialize Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance Data.Serialize.Serialize Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance GHC.Classes.Eq Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance GHC.Classes.Eq Haskoin.Network.Common.AddressNetwork
+ Haskoin.Network.Common: instance GHC.Classes.Eq Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance GHC.Classes.Eq Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance GHC.Classes.Eq Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance GHC.Internal.Generics.Generic Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance GHC.Internal.Generics.Generic Haskoin.Network.Common.AddressNetwork
+ Haskoin.Network.Common: instance GHC.Internal.Generics.Generic Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance GHC.Internal.Generics.Generic Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance GHC.Internal.Generics.Generic Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance GHC.Internal.Read.Read Haskoin.Network.Common.AddressNetwork
+ Haskoin.Network.Common: instance GHC.Internal.Read.Read Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance GHC.Internal.Read.Read Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance GHC.Internal.Read.Read Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: instance GHC.Internal.Show.Show Haskoin.Network.Common.AddrV2
+ Haskoin.Network.Common: instance GHC.Internal.Show.Show Haskoin.Network.Common.AddressNetwork
+ Haskoin.Network.Common: instance GHC.Internal.Show.Show Haskoin.Network.Common.FeeFilter
+ Haskoin.Network.Common: instance GHC.Internal.Show.Show Haskoin.Network.Common.NetworkAddressV2
+ Haskoin.Network.Common: instance GHC.Internal.Show.Show Haskoin.Network.Common.SendTxRcncl
+ Haskoin.Network.Common: maxAddrV2Size :: Word64
+ Haskoin.Network.Common: maxListSize :: Word64
+ Haskoin.Network.Common: maxUserAgentLength :: Int
+ Haskoin.Network.Common: newtype AddrV2
+ Haskoin.Network.Common: newtype FeeFilter
+ Haskoin.Network.Common: nodeCompactFilters :: Word64
+ Haskoin.Network.Common: nodeNetworkLimited :: Word64
+ Haskoin.Network.Common: nodeP2PV2 :: Word64
+ Haskoin.Network.Common: putVarIntBytes :: MonadPut m => ByteString -> m ()
+ Haskoin.Network.Common: putVarIntList :: MonadPut m => (a -> m ()) -> [a] -> m ()
+ Haskoin.Network.Common: toNetworkAddressV2 :: NetworkAddressTime -> NetworkAddressV2
+ Haskoin.Network.CompactBlock: BlockTxn :: !BlockHash -> ![Tx] -> BlockTxn
+ Haskoin.Network.CompactBlock: CompactBlock :: !BlockHeader -> !Word64 -> ![Word64] -> ![PrefilledTx] -> CompactBlock
+ Haskoin.Network.CompactBlock: GetBlockTxn :: !BlockHash -> ![Word32] -> GetBlockTxn
+ Haskoin.Network.CompactBlock: PrefilledTx :: !Word32 -> !Tx -> PrefilledTx
+ Haskoin.Network.CompactBlock: SendCompact :: !Bool -> !Word64 -> SendCompact
+ Haskoin.Network.CompactBlock: [announce] :: SendCompact -> !Bool
+ Haskoin.Network.CompactBlock: [hash] :: BlockTxn -> !BlockHash
+ Haskoin.Network.CompactBlock: [header] :: CompactBlock -> !BlockHeader
+ Haskoin.Network.CompactBlock: [index] :: PrefilledTx -> !Word32
+ Haskoin.Network.CompactBlock: [indexes] :: GetBlockTxn -> ![Word32]
+ Haskoin.Network.CompactBlock: [nonce] :: CompactBlock -> !Word64
+ Haskoin.Network.CompactBlock: [prefilled] :: CompactBlock -> ![PrefilledTx]
+ Haskoin.Network.CompactBlock: [shortIds] :: CompactBlock -> ![Word64]
+ Haskoin.Network.CompactBlock: [transactions] :: BlockTxn -> ![Tx]
+ Haskoin.Network.CompactBlock: [tx] :: PrefilledTx -> !Tx
+ Haskoin.Network.CompactBlock: [version] :: SendCompact -> !Word64
+ Haskoin.Network.CompactBlock: data BlockTxn
+ Haskoin.Network.CompactBlock: data CompactBlock
+ Haskoin.Network.CompactBlock: data GetBlockTxn
+ Haskoin.Network.CompactBlock: data PrefilledTx
+ Haskoin.Network.CompactBlock: data SendCompact
+ Haskoin.Network.CompactBlock: instance Control.DeepSeq.NFData Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance Control.DeepSeq.NFData Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance Control.DeepSeq.NFData Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance Control.DeepSeq.NFData Haskoin.Network.CompactBlock.PrefilledTx
+ Haskoin.Network.CompactBlock: instance Control.DeepSeq.NFData Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance Data.Binary.Class.Binary Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance Data.Binary.Class.Binary Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance Data.Binary.Class.Binary Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance Data.Binary.Class.Binary Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance Data.Bytes.Serial.Serial Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance Data.Bytes.Serial.Serial Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance Data.Bytes.Serial.Serial Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance Data.Bytes.Serial.Serial Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance Data.Serialize.Serialize Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance Data.Serialize.Serialize Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance Data.Serialize.Serialize Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance Data.Serialize.Serialize Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance GHC.Classes.Eq Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance GHC.Classes.Eq Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance GHC.Classes.Eq Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance GHC.Classes.Eq Haskoin.Network.CompactBlock.PrefilledTx
+ Haskoin.Network.CompactBlock: instance GHC.Classes.Eq Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Generics.Generic Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Generics.Generic Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Generics.Generic Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Generics.Generic Haskoin.Network.CompactBlock.PrefilledTx
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Generics.Generic Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Read.Read Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Show.Show Haskoin.Network.CompactBlock.BlockTxn
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Show.Show Haskoin.Network.CompactBlock.CompactBlock
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Show.Show Haskoin.Network.CompactBlock.GetBlockTxn
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Show.Show Haskoin.Network.CompactBlock.PrefilledTx
+ Haskoin.Network.CompactBlock: instance GHC.Internal.Show.Show Haskoin.Network.CompactBlock.SendCompact
+ Haskoin.Network.CompactBlock: maxShortId :: Word64
+ Haskoin.Network.Data: [bip34Hash] :: Network -> !Maybe BlockHash
+ Haskoin.Network.Data: [bip34Height] :: Network -> !BlockHeight
+ Haskoin.Network.Data: [genesisTx] :: Network -> !Tx
+ Haskoin.Network.Data: [maxMessageSize] :: Network -> !Word32
+ Haskoin.Network.Message: MAddrV2 :: !AddrV2 -> Message
+ Haskoin.Network.Message: MBlockTxn :: !BlockTxn -> Message
+ Haskoin.Network.Message: MCFCheckpt :: !CFCheckpt -> Message
+ Haskoin.Network.Message: MCFHeaders :: !CFHeaders -> Message
+ Haskoin.Network.Message: MCFilter :: !CFilter -> Message
+ Haskoin.Network.Message: MCompactBlock :: !CompactBlock -> Message
+ Haskoin.Network.Message: MFeeFilter :: !FeeFilter -> Message
+ Haskoin.Network.Message: MGetBlockTxn :: !GetBlockTxn -> Message
+ Haskoin.Network.Message: MGetCFCheckpt :: !GetCFCheckpt -> Message
+ Haskoin.Network.Message: MGetCFHeaders :: !GetCFHeaders -> Message
+ Haskoin.Network.Message: MGetCFilters :: !GetCFilters -> Message
+ Haskoin.Network.Message: MSendAddrV2 :: Message
+ Haskoin.Network.Message: MSendCompact :: !SendCompact -> Message
+ Haskoin.Network.Message: MSendTxRcncl :: !SendTxRcncl -> Message
+ Haskoin.Network.Message: MWtxidRelay :: Message
+ Haskoin.Script.Common: isMinimalPush :: ScriptOp -> Bool
+ Haskoin.Script.Common: pattern OP_CHECKSIGADD :: ScriptOp
+ Haskoin.Script.Common: pushOpData :: ScriptOp -> Maybe ByteString
+ Haskoin.Script.SigHash: sigHashDefault :: SigHash
+ Haskoin.Script.Standard: NestedWitnessInput :: !RedeemScript -> ScriptInput
+ Haskoin.Script.Standard: [pushes] :: ScriptOutput -> ![ByteString]
+ Haskoin.Script.Standard: isNestedWitnessInput :: ScriptInput -> Bool
+ Haskoin.Script.Standard: maxMultisigKeys :: Int
+ Haskoin.Transaction.Builder: InputMulSig :: Int -> Int -> InputKind
+ Haskoin.Transaction.Builder: InputNestedWitnessMulSig :: Int -> Int -> InputKind
+ Haskoin.Transaction.Builder: InputNestedWitnessPKHash :: InputKind
+ Haskoin.Transaction.Builder: InputPK :: InputKind
+ Haskoin.Transaction.Builder: InputPKHash :: InputKind
+ Haskoin.Transaction.Builder: InputTaproot :: InputKind
+ Haskoin.Transaction.Builder: InputUncompressedPKHash :: InputKind
+ Haskoin.Transaction.Builder: InputWitnessMulSig :: Int -> Int -> InputKind
+ Haskoin.Transaction.Builder: InputWitnessPKHash :: InputKind
+ Haskoin.Transaction.Builder: OutputPKHash :: OutputKind
+ Haskoin.Transaction.Builder: OutputScript :: Int -> OutputKind
+ Haskoin.Transaction.Builder: OutputScript32Hash :: OutputKind
+ Haskoin.Transaction.Builder: OutputScriptHash :: OutputKind
+ Haskoin.Transaction.Builder: OutputTaproot :: OutputKind
+ Haskoin.Transaction.Builder: OutputWitnessPKHash :: OutputKind
+ Haskoin.Transaction.Builder: OutputWitnessScriptHash :: OutputKind
+ Haskoin.Transaction.Builder: coinInputKind :: Coin c => c -> InputKind
+ Haskoin.Transaction.Builder: data InputKind
+ Haskoin.Transaction.Builder: data OutputKind
+ Haskoin.Transaction.Builder: guessTxWeight :: [InputKind] -> [OutputKind] -> Int
+ Haskoin.Transaction.Builder: inputWeight :: InputKind -> (Int, Int)
+ Haskoin.Transaction.Builder: instance GHC.Classes.Eq Haskoin.Transaction.Builder.InputKind
+ Haskoin.Transaction.Builder: instance GHC.Classes.Eq Haskoin.Transaction.Builder.OutputKind
+ Haskoin.Transaction.Builder: instance GHC.Internal.Read.Read Haskoin.Transaction.Builder.InputKind
+ Haskoin.Transaction.Builder: instance GHC.Internal.Read.Read Haskoin.Transaction.Builder.OutputKind
+ Haskoin.Transaction.Builder: instance GHC.Internal.Show.Show Haskoin.Transaction.Builder.InputKind
+ Haskoin.Transaction.Builder: instance GHC.Internal.Show.Show Haskoin.Transaction.Builder.OutputKind
+ Haskoin.Transaction.Builder: outputSize :: OutputKind -> Int
+ Haskoin.Transaction.Builder: signTaprootInput :: Ctx -> Tx -> [TxOut] -> Int -> SigHash -> SecKey -> Either String Tx
+ Haskoin.Transaction.Builder.Sign: signTaprootInput :: Ctx -> Tx -> [TxOut] -> Int -> SigHash -> SecKey -> Either String Tx
+ Haskoin.Transaction.Common: hasWitness :: Tx -> Bool
+ Haskoin.Transaction.Common: parseLegacyTx :: MonadGet m => m Tx
+ Haskoin.Transaction.Common: txVirtualSize :: Tx -> Int
+ Haskoin.Transaction.Common: txWeight :: Tx -> Int
+ Haskoin.Transaction.Common: txWitnessHash :: Tx -> TxHash
+ Haskoin.Transaction.Genesis: genesisCoinbase :: ByteString -> ByteString -> Tx
+ Haskoin.Transaction.Genesis: mainnetGenesisTx :: Tx
+ Haskoin.Transaction.Genesis: testnet4GenesisTx :: Tx
+ Haskoin.Transaction.Psbt: Derivation :: !Fingerprint -> !DerivPath -> Derivation
+ Haskoin.Transaction.Psbt: Key :: !Word8 -> !ByteString -> Key
+ Haskoin.Transaction.Psbt: Modifiable :: !Bool -> !Bool -> !Bool -> !Word8 -> Modifiable
+ Haskoin.Transaction.Psbt: Proprietary :: !ByteString -> !Word64 -> !ByteString -> Proprietary
+ Haskoin.Transaction.Psbt: Psbt :: !PsbtVersion -> !Tx -> !PsbtMap -> ![PsbtMap] -> ![PsbtMap] -> Psbt
+ Haskoin.Transaction.Psbt: Signer :: (PublicKey -> Derivation -> Maybe SecKey) -> (Hash160 -> Maybe PrivateKey) -> [SigHash] -> Signer
+ Haskoin.Transaction.Psbt: TapDerivation :: ![Digest SHA256] -> !Derivation -> TapDerivation
+ Haskoin.Transaction.Psbt: TapLeafScript :: !ByteString -> !Script -> !TapLeafVersion -> TapLeafScript
+ Haskoin.Transaction.Psbt: Version0 :: PsbtVersion
+ Haskoin.Transaction.Psbt: Version2 :: PsbtVersion
+ Haskoin.Transaction.Psbt: [acceptedSigHashes] :: Signer -> [SigHash]
+ Haskoin.Transaction.Psbt: [controlBlock] :: TapLeafScript -> !ByteString
+ Haskoin.Transaction.Psbt: [derivation] :: TapDerivation -> !Derivation
+ Haskoin.Transaction.Psbt: [extra] :: Proprietary -> !ByteString
+ Haskoin.Transaction.Psbt: [fingerprint] :: Derivation -> !Fingerprint
+ Haskoin.Transaction.Psbt: [globals] :: Psbt -> !PsbtMap
+ Haskoin.Transaction.Psbt: [hasSigHashSingle] :: Modifiable -> !Bool
+ Haskoin.Transaction.Psbt: [identifier] :: Proprietary -> !ByteString
+ Haskoin.Transaction.Psbt: [inputsModifiable] :: Modifiable -> !Bool
+ Haskoin.Transaction.Psbt: [inputs] :: Psbt -> ![PsbtMap]
+ Haskoin.Transaction.Psbt: [keyData] :: Key -> !ByteString
+ Haskoin.Transaction.Psbt: [keyOfHash] :: Signer -> Hash160 -> Maybe PrivateKey
+ Haskoin.Transaction.Psbt: [keyOfRecord] :: Signer -> PublicKey -> Derivation -> Maybe SecKey
+ Haskoin.Transaction.Psbt: [keyType] :: Key -> !Word8
+ Haskoin.Transaction.Psbt: [leafHashes] :: TapDerivation -> ![Digest SHA256]
+ Haskoin.Transaction.Psbt: [leafVersion] :: TapLeafScript -> !TapLeafVersion
+ Haskoin.Transaction.Psbt: [outputsModifiable] :: Modifiable -> !Bool
+ Haskoin.Transaction.Psbt: [outputs] :: Psbt -> ![PsbtMap]
+ Haskoin.Transaction.Psbt: [path] :: Derivation -> !DerivPath
+ Haskoin.Transaction.Psbt: [script] :: TapLeafScript -> !Script
+ Haskoin.Transaction.Psbt: [subtype] :: Proprietary -> !Word64
+ Haskoin.Transaction.Psbt: [undefinedBits] :: Modifiable -> !Word8
+ Haskoin.Transaction.Psbt: [unsignedTx] :: Psbt -> !Tx
+ Haskoin.Transaction.Psbt: [version] :: Psbt -> !PsbtVersion
+ Haskoin.Transaction.Psbt: addInput :: TxIn -> PsbtMap -> Psbt -> Either String Psbt
+ Haskoin.Transaction.Psbt: addOutput :: TxOut -> PsbtMap -> Psbt -> Either String Psbt
+ Haskoin.Transaction.Psbt: afterSignature :: SigHash -> Psbt -> Psbt
+ Haskoin.Transaction.Psbt: combinePsbt :: Psbt -> Psbt -> Either String Psbt
+ Haskoin.Transaction.Psbt: data Derivation
+ Haskoin.Transaction.Psbt: data Key
+ Haskoin.Transaction.Psbt: data Modifiable
+ Haskoin.Transaction.Psbt: data Proprietary
+ Haskoin.Transaction.Psbt: data Psbt
+ Haskoin.Transaction.Psbt: data PsbtVersion
+ Haskoin.Transaction.Psbt: data Signer
+ Haskoin.Transaction.Psbt: data TapDerivation
+ Haskoin.Transaction.Psbt: data TapLeafScript
+ Haskoin.Transaction.Psbt: decodePsbt :: ByteString -> Either String Psbt
+ Haskoin.Transaction.Psbt: decodePsbtBase64 :: ByteString -> Either String Psbt
+ Haskoin.Transaction.Psbt: defaultSigHashes :: [SigHash]
+ Haskoin.Transaction.Psbt: deletePartialSignatures :: PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: deleteSigHashType :: PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: deleteTxModifiable :: PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: encodePsbt :: Psbt -> ByteString
+ Haskoin.Transaction.Psbt: encodePsbtBase64 :: Psbt -> ByteString
+ Haskoin.Transaction.Psbt: fallbackLockTime :: PsbtMap -> Either String (Maybe Word32)
+ Haskoin.Transaction.Psbt: finalScriptSig :: PsbtMap -> Maybe ByteString
+ Haskoin.Transaction.Psbt: finalScriptWitness :: PsbtMap -> Either String (Maybe WitnessStack)
+ Haskoin.Transaction.Psbt: finalizePsbt :: Network -> Ctx -> Psbt -> Either String Psbt
+ Haskoin.Transaction.Psbt: hasPartialSignatures :: PsbtMap -> Bool
+ Haskoin.Transaction.Psbt: inputDerivations :: Ctx -> PsbtMap -> Either String [(PublicKey, Derivation)]
+ Haskoin.Transaction.Psbt: inputRedeemScript :: PsbtMap -> Either String (Maybe Script)
+ Haskoin.Transaction.Psbt: inputTapDerivations :: Ctx -> PsbtMap -> Either String [(PubKey, TapDerivation)]
+ Haskoin.Transaction.Psbt: inputTapInternalKey :: Ctx -> PsbtMap -> Either String (Maybe PubKey)
+ Haskoin.Transaction.Psbt: inputTapMerkleRoot :: PsbtMap -> Either String (Maybe (Digest SHA256))
+ Haskoin.Transaction.Psbt: inputWitnessScript :: PsbtMap -> Either String (Maybe Script)
+ Haskoin.Transaction.Psbt: insertFinalScriptSig :: ByteString -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertFinalScriptWitness :: WitnessStack -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertInputDerivation :: Ctx -> PublicKey -> Derivation -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertInputRedeemScript :: Script -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertInputTapDerivation :: Ctx -> PubKey -> TapDerivation -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertInputTapInternalKey :: Ctx -> PubKey -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertInputTapMerkleRoot :: Digest SHA256 -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertInputWitnessScript :: Script -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertNonWitnessUtxo :: Tx -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertOutputDerivation :: Ctx -> PublicKey -> Derivation -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertOutputRedeemScript :: Script -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertOutputTapDerivation :: Ctx -> PubKey -> TapDerivation -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertOutputTapInternalKey :: Ctx -> PubKey -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertOutputTapTree :: [TapLeaf] -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertOutputWitnessScript :: Script -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertPartialSignature :: Ctx -> PublicKey -> ByteString -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertProprietary :: ByteString -> Word64 -> ByteString -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertRequiredHeightLockTime :: Word32 -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertRequiredTimeLockTime :: Word32 -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertSigHashType :: SigHash -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertTapKeySig :: TaprootSignature -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertTapLeafScript :: TapLeafScript -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertTapScriptSig :: Ctx -> PubKey -> Digest SHA256 -> TaprootSignature -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertTxModifiable :: Modifiable -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertWitnessUtxo :: TxOut -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: insertXpubRecord :: Network -> Ctx -> XPubKey -> Derivation -> PsbtMap -> PsbtMap
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.Derivation
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.Key
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.Modifiable
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.Proprietary
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.Psbt
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.PsbtVersion
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.TapLeafScript
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.Derivation
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.Key
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.Modifiable
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.Proprietary
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.Psbt
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.PsbtVersion
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.TapDerivation
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.TapLeafScript
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Ord Haskoin.Transaction.Psbt.Key
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Ord Haskoin.Transaction.Psbt.PsbtVersion
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Base.Applicative Haskoin.Transaction.Psbt.BytesParser
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Base.Functor Haskoin.Transaction.Psbt.BytesParser
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Base.Monad Haskoin.Transaction.Psbt.BytesParser
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.Derivation
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.Key
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.Modifiable
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.Proprietary
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.Psbt
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.PsbtVersion
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.TapDerivation
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.TapLeafScript
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.Derivation
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.Key
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.Modifiable
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.Proprietary
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.Psbt
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.PsbtVersion
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.TapLeafScript
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.Derivation
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.Key
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.Modifiable
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.Proprietary
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.Psbt
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.PsbtVersion
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.TapDerivation
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.TapLeafScript
+ Haskoin.Transaction.Psbt: isPsbt :: ByteString -> Bool
+ Haskoin.Transaction.Psbt: keySigner :: Ctx -> SecKey -> Signer
+ Haskoin.Transaction.Psbt: lookupProprietary :: ByteString -> Word64 -> PsbtMap -> Maybe ByteString
+ Haskoin.Transaction.Psbt: nonWitnessUtxo :: PsbtMap -> Either String (Maybe Tx)
+ Haskoin.Transaction.Psbt: outputDerivations :: Ctx -> PsbtMap -> Either String [(PublicKey, Derivation)]
+ Haskoin.Transaction.Psbt: outputRedeemScript :: PsbtMap -> Either String (Maybe Script)
+ Haskoin.Transaction.Psbt: outputTapDerivations :: Ctx -> PsbtMap -> Either String [(PubKey, TapDerivation)]
+ Haskoin.Transaction.Psbt: outputTapInternalKey :: Ctx -> PsbtMap -> Either String (Maybe PubKey)
+ Haskoin.Transaction.Psbt: outputTapTree :: PsbtMap -> Either String (Maybe [TapLeaf])
+ Haskoin.Transaction.Psbt: outputWitnessScript :: PsbtMap -> Either String (Maybe Script)
+ Haskoin.Transaction.Psbt: partialSignatures :: Ctx -> PsbtMap -> Either String [(PublicKey, ByteString)]
+ Haskoin.Transaction.Psbt: proprietaryKey :: Proprietary -> Key
+ Haskoin.Transaction.Psbt: proprietaryOf :: Key -> Either String (Maybe Proprietary)
+ Haskoin.Transaction.Psbt: psbtId :: Psbt -> TxHash
+ Haskoin.Transaction.Psbt: psbtMagic :: ByteString
+ Haskoin.Transaction.Psbt: psbtOf :: Tx -> Psbt
+ Haskoin.Transaction.Psbt: psbtOfMaps :: PsbtMap -> [PsbtMap] -> [PsbtMap] -> Either String Psbt
+ Haskoin.Transaction.Psbt: psbtTransaction :: Psbt -> Either String Tx
+ Haskoin.Transaction.Psbt: requiredHeightLockTime :: PsbtMap -> Either String (Maybe Word32)
+ Haskoin.Transaction.Psbt: requiredTimeLockTime :: PsbtMap -> Either String (Maybe Word32)
+ Haskoin.Transaction.Psbt: setInputSequence :: Int -> Word32 -> Psbt -> Either String Psbt
+ Haskoin.Transaction.Psbt: sigHashType :: PsbtMap -> Either String (Maybe SigHash)
+ Haskoin.Transaction.Psbt: signPsbt :: Network -> Ctx -> Signer -> Psbt -> Either String Psbt
+ Haskoin.Transaction.Psbt: tapKeySig :: PsbtMap -> Either String (Maybe TaprootSignature)
+ Haskoin.Transaction.Psbt: tapLeafScripts :: Ctx -> PsbtMap -> Either String [TapLeafScript]
+ Haskoin.Transaction.Psbt: tapScriptSigs :: Ctx -> PsbtMap -> Either String [((PubKey, Digest SHA256), TaprootSignature)]
+ Haskoin.Transaction.Psbt: txModifiable :: PsbtMap -> Either String (Maybe Modifiable)
+ Haskoin.Transaction.Psbt: type PsbtMap = Map Key ByteString
+ Haskoin.Transaction.Psbt: withVersion :: PsbtVersion -> Psbt -> Psbt
+ Haskoin.Transaction.Psbt: witnessUtxo :: PsbtMap -> Either String (Maybe TxOut)
+ Haskoin.Transaction.Psbt: xPrvSigner :: Ctx -> XPrvKey -> Maybe Derivation -> Signer
+ Haskoin.Transaction.Psbt: xpubRecords :: Network -> Ctx -> PsbtMap -> Either String [(XPubKey, Derivation)]
+ Haskoin.Transaction.Segwit: instance GHC.Internal.Show.Show Haskoin.Transaction.Segwit.WitnessProgram
+ Haskoin.Transaction.Segwit: instance GHC.Internal.Show.Show Haskoin.Transaction.Segwit.WitnessProgramPKH
+ Haskoin.Transaction.Taproot: TapLeaf :: !Word8 -> !TapLeafVersion -> !Script -> TapLeaf
+ Haskoin.Transaction.Taproot: TaprootSignature :: !SchnorrSig -> !SigHash -> TaprootSignature
+ Haskoin.Transaction.Taproot: [depth] :: TapLeaf -> !Word8
+ Haskoin.Transaction.Taproot: [hash] :: TaprootSignature -> !SigHash
+ Haskoin.Transaction.Taproot: [sig] :: TaprootSignature -> !SchnorrSig
+ Haskoin.Transaction.Taproot: [version] :: TapLeaf -> !TapLeafVersion
+ Haskoin.Transaction.Taproot: data TapLeaf
+ Haskoin.Transaction.Taproot: data TaprootSignature
+ Haskoin.Transaction.Taproot: decodeControlBlock :: Ctx -> ByteString -> Either String (Word8, PubKey, [Digest SHA256])
+ Haskoin.Transaction.Taproot: decodeTaprootSig :: ByteString -> Either String TaprootSignature
+ Haskoin.Transaction.Taproot: encodeControlBlock :: Ctx -> Word8 -> PubKey -> [Digest SHA256] -> ByteString
+ Haskoin.Transaction.Taproot: encodeTaprootSig :: TaprootSignature -> ByteString
+ Haskoin.Transaction.Taproot: instance GHC.Classes.Eq Haskoin.Transaction.Taproot.MAST
+ Haskoin.Transaction.Taproot: instance GHC.Classes.Eq Haskoin.Transaction.Taproot.TapLeaf
+ Haskoin.Transaction.Taproot: instance GHC.Classes.Eq Haskoin.Transaction.Taproot.TaprootSignature
+ Haskoin.Transaction.Taproot: instance GHC.Internal.Show.Show Haskoin.Transaction.Taproot.ScriptPathData
+ Haskoin.Transaction.Taproot: instance GHC.Internal.Show.Show Haskoin.Transaction.Taproot.TapLeaf
+ Haskoin.Transaction.Taproot: instance GHC.Internal.Show.Show Haskoin.Transaction.Taproot.TaprootSignature
+ Haskoin.Transaction.Taproot: instance GHC.Internal.Show.Show Haskoin.Transaction.Taproot.TaprootWitness
+ Haskoin.Transaction.Taproot: isTaprootSigHash :: SigHash -> Bool
+ Haskoin.Transaction.Taproot: keyParity :: Ctx -> PubKey -> Word8
+ Haskoin.Transaction.Taproot: leafHash :: TapLeafVersion -> Script -> Digest SHA256
+ Haskoin.Transaction.Taproot: maxControlBlockNodes :: Int
+ Haskoin.Transaction.Taproot: tapTreeFromLeaves :: [TapLeaf] -> Maybe MAST
+ Haskoin.Transaction.Taproot: tapTreeLeaves :: MAST -> Maybe [TapLeaf]
+ Haskoin.Transaction.Taproot: taprootOutputSecKey :: Ctx -> SecKey -> Maybe MAST -> Maybe SecKey
+ Haskoin.Transaction.Taproot: taprootSigHash :: Tx -> [TxOut] -> Int -> SigHash -> Maybe ByteString -> Maybe (Digest SHA256, Word32) -> Either String Hash256
+ Haskoin.Transaction.Taproot: tapscriptLeafVersion :: TapLeafVersion
+ Haskoin.Util.Arbitrary.Message: arbitraryBlockTxn :: Network -> Ctx -> Gen BlockTxn
+ Haskoin.Util.Arbitrary.Message: arbitraryCFCheckpt :: Gen CFCheckpt
+ Haskoin.Util.Arbitrary.Message: arbitraryCFHeaders :: Gen CFHeaders
+ Haskoin.Util.Arbitrary.Message: arbitraryCFilter :: Gen CFilter
+ Haskoin.Util.Arbitrary.Message: arbitraryCompactBlock :: Network -> Ctx -> Gen CompactBlock
+ Haskoin.Util.Arbitrary.Message: arbitraryGetBlockTxn :: Gen GetBlockTxn
+ Haskoin.Util.Arbitrary.Message: arbitraryGetCFCheckpt :: Gen GetCFCheckpt
+ Haskoin.Util.Arbitrary.Message: arbitraryGetCFHeaders :: Gen GetCFHeaders
+ Haskoin.Util.Arbitrary.Message: arbitraryGetCFilters :: Gen GetCFilters
+ Haskoin.Util.Arbitrary.Message: arbitraryPositions :: Gen [Word32]
+ Haskoin.Util.Arbitrary.Message: arbitrarySendCompact :: Gen SendCompact
+ Haskoin.Util.Arbitrary.Network: arbitraryAddrV2 :: Gen AddrV2
+ Haskoin.Util.Arbitrary.Network: arbitraryAddressNetwork :: Gen AddressNetwork
+ Haskoin.Util.Arbitrary.Network: arbitraryFeeFilter :: Gen FeeFilter
+ Haskoin.Util.Arbitrary.Network: arbitraryNetworkAddressV2 :: Gen NetworkAddressV2
+ Haskoin.Util.Arbitrary.Network: arbitraryOtherCommand :: Gen ByteString
+ Haskoin.Util.Arbitrary.Network: arbitrarySendTxRcncl :: Gen SendTxRcncl
+ Haskoin.Util.Arbitrary.Script: arbitraryNestedWitnessInput :: Gen ScriptInput
+ Haskoin.Util.Arbitrary.Script: arbitraryPushOp :: Gen ScriptOp
+ Haskoin.Util.Arbitrary.Transaction: [getTestCoinKind] :: TestCoin -> InputKind
+ Haskoin.Util.Arbitrary.Transaction: arbitraryInputKind :: Gen InputKind
+ Haskoin.Util.Arbitrary.Transaction: arbitraryOutputKind :: Gen OutputKind
+ Haskoin.Util.Arbitrary.Transaction: arbitraryOutputsWithin :: Network -> Ctx -> Word64 -> Int -> Gen [TxOut]
+ Haskoin.Util.Arbitrary.Transaction: arbitraryPsbt :: Network -> Ctx -> Gen Psbt
+ Haskoin.Util.Arbitrary.Transaction: arbitraryTaprootSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)
+ Haskoin.Util.Arbitrary.Transaction: data TestCoin
+ Haskoin.Util.Arbitrary.Transaction: legacySingle :: Network -> SigInput -> Bool
- Haskoin.Address: pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Address
+ Haskoin.Address: pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Maybe Address
- Haskoin.Address: pubKeyWitnessAddr :: Ctx -> PublicKey -> Address
+ Haskoin.Address: pubKeyWitnessAddr :: Ctx -> PublicKey -> Maybe Address
- Haskoin.Block.Headers: computeAsertBits :: Integer -> Word32 -> Integer -> Integer -> Word32
+ Haskoin.Block.Headers: computeAsertBits :: Integer -> Integer -> Word32 -> Integer -> Integer -> Word32
- Haskoin.Block.Headers: genesisBlock :: Network -> Ctx -> Block
+ Haskoin.Block.Headers: genesisBlock :: Network -> Block
- Haskoin.Block.Headers: validBlock :: Network -> Timestamp -> BlockNode -> BlockNode -> [BlockNode] -> BlockHeader -> BlockNode -> Either String BlockNode
+ Haskoin.Block.Headers: validBlock :: Network -> Timestamp -> BlockNode -> BlockNode -> [BlockNode] -> BlockHeader -> BlockNode -> Word32 -> Either String BlockNode
- Haskoin.Block.Merkle: buildMerkleRoot :: [TxHash] -> MerkleRoot
+ Haskoin.Block.Merkle: buildMerkleRoot :: [TxHash] -> Either String MerkleRoot
- Haskoin.Network.Common: Reject :: !MessageCommand -> !RejectCode -> !VarString -> !ByteString -> Reject
+ Haskoin.Network.Common: Reject :: !MessageCommand -> !RejectCode -> !VarString -> !Maybe Hash256 -> Reject
- Haskoin.Network.Common: [extra] :: Reject -> !ByteString
+ Haskoin.Network.Common: [extra] :: Reject -> !Maybe Hash256
- Haskoin.Network.Data: Network :: !String -> !Word8 -> !Word8 -> !Word8 -> !Word32 -> !Word32 -> !Word32 -> !BlockHeader -> !Int -> !Word64 -> !ByteString -> !Int -> !Bool -> !Bool -> !Bool -> !Integer -> !(BlockHeight, BlockHash) -> !BlockHeight -> !BlockHeight -> !Word32 -> !Word32 -> ![(BlockHeight, BlockHash)] -> !Word32 -> ![String] -> !Maybe Word32 -> !Maybe Word32 -> !Maybe Word32 -> !Maybe Word32 -> !Integer -> !Bool -> !Maybe Text -> !Maybe Text -> !Bool -> !Word32 -> Network
+ Haskoin.Network.Data: Network :: !String -> !Word8 -> !Word8 -> !Word8 -> !Word32 -> !Word32 -> !Word32 -> !BlockHeader -> !Tx -> !Int -> !Word32 -> !Word64 -> !ByteString -> !Int -> !Bool -> !Bool -> !Bool -> !Integer -> !BlockHeight -> !Maybe BlockHash -> !BlockHeight -> !BlockHeight -> !Word32 -> !Word32 -> ![(BlockHeight, BlockHash)] -> !Word32 -> ![String] -> !Maybe Word32 -> !Maybe Word32 -> !Maybe Word32 -> !Maybe Word32 -> !Integer -> !Bool -> !Maybe Text -> !Maybe Text -> !Bool -> !Word32 -> Network
- Haskoin.Script.Common: intToScriptOp :: Int -> ScriptOp
+ Haskoin.Script.Common: intToScriptOp :: Int -> Either String ScriptOp
- Haskoin.Script.SigHash: txSigHash :: Network -> Tx -> Script -> Word64 -> Int -> SigHash -> Hash256
+ Haskoin.Script.SigHash: txSigHash :: Network -> Tx -> Script -> Word64 -> Int -> SigHash -> Either String Hash256
- Haskoin.Script.SigHash: txSigHashForkId :: Network -> Tx -> Script -> Word64 -> Int -> SigHash -> Hash256
+ Haskoin.Script.SigHash: txSigHashForkId :: Network -> Tx -> Script -> Word64 -> Int -> SigHash -> Either String Hash256
- Haskoin.Script.Standard: DataCarrier :: !ByteString -> ScriptOutput
+ Haskoin.Script.Standard: DataCarrier :: ![ByteString] -> ScriptOutput
- Haskoin.Script.Standard: encodeOutput :: Ctx -> ScriptOutput -> Script
+ Haskoin.Script.Standard: encodeOutput :: Ctx -> ScriptOutput -> Either String Script
- Haskoin.Transaction.Builder: chooseCoins :: Coin c => Word64 -> Word64 -> Int -> Bool -> [c] -> Either String ([c], Word64)
+ Haskoin.Transaction.Builder: chooseCoins :: Coin c => Word64 -> Word64 -> [OutputKind] -> Bool -> [c] -> Either String ([c], Word64)
- Haskoin.Transaction.Builder: chooseCoinsSink :: forall (m :: Type -> Type) c. (Monad m, Coin c) => Word64 -> Word64 -> Int -> Bool -> ConduitT c Void m (Either String ([c], Word64))
+ Haskoin.Transaction.Builder: chooseCoinsSink :: forall (m :: Type -> Type) c. (Monad m, Coin c) => Word64 -> Word64 -> [OutputKind] -> Bool -> ConduitT c Void m (Either String ([c], Word64))
- Haskoin.Transaction.Builder: greedyAddSink :: forall (m :: Type -> Type) c. (Monad m, Coin c) => Word64 -> (Int -> Word64) -> Bool -> ConduitT c Void m (Maybe ([c], Word64))
+ Haskoin.Transaction.Builder: greedyAddSink :: forall (m :: Type -> Type) c. (Monad m, Coin c) => Word64 -> ([c] -> Word64) -> Bool -> ConduitT c Void m (Maybe ([c], Word64))
- Haskoin.Transaction.Builder: guessTxFee :: Word64 -> Int -> Int -> Word64
+ Haskoin.Transaction.Builder: guessTxFee :: Word64 -> [InputKind] -> [OutputKind] -> Word64
- Haskoin.Transaction.Builder: guessTxSize :: Int -> [(Int, Int)] -> Int -> Int -> Int
+ Haskoin.Transaction.Builder: guessTxSize :: [InputKind] -> [OutputKind] -> Int
- Haskoin.Transaction.Builder: makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> TxSignature
+ Haskoin.Transaction.Builder: makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String TxSignature
- Haskoin.Transaction.Builder: verifyStdInput :: Network -> Ctx -> Tx -> Int -> ScriptOutput -> Word64 -> Bool
+ Haskoin.Transaction.Builder: verifyStdInput :: Network -> Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Int -> Bool
- Haskoin.Transaction.Builder.Sign: makeSigHash :: Network -> Ctx -> Tx -> Int -> ScriptOutput -> Word64 -> SigHash -> Maybe RedeemScript -> Hash256
+ Haskoin.Transaction.Builder.Sign: makeSigHash :: Network -> Ctx -> Tx -> Int -> ScriptOutput -> Word64 -> SigHash -> Maybe RedeemScript -> Either String Hash256
- Haskoin.Transaction.Builder.Sign: makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> TxSignature
+ Haskoin.Transaction.Builder.Sign: makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String TxSignature
- Haskoin.Transaction.Taproot: KeyPathSpend :: ByteString -> TaprootWitness
+ Haskoin.Transaction.Taproot: KeyPathSpend :: ByteString -> Maybe ByteString -> TaprootWitness
- Haskoin.Transaction.Taproot: ScriptPathData :: Maybe ByteString -> [ByteString] -> Script -> Bool -> Word8 -> PubKey -> [ByteString] -> ScriptPathData
+ Haskoin.Transaction.Taproot: ScriptPathData :: Maybe ByteString -> [ByteString] -> Script -> Bool -> Word8 -> PubKey -> [Digest SHA256] -> ScriptPathData
- Haskoin.Transaction.Taproot: [control] :: ScriptPathData -> [ByteString]
+ Haskoin.Transaction.Taproot: [control] :: ScriptPathData -> [Digest SHA256]
- Haskoin.Util.Arbitrary.Network: arbitraryBloomFilter :: Gen (Int, Double, BloomFilter)
+ Haskoin.Util.Arbitrary.Network: arbitraryBloomFilter :: Gen BloomFilter
- Haskoin.Util.Arbitrary.Transaction: TestCoin :: Word64 -> TestCoin
+ Haskoin.Util.Arbitrary.Transaction: TestCoin :: Word64 -> InputKind -> TestCoin
- Haskoin.Util.Helpers: convertBits :: Bool -> Int -> Int -> [Word] -> ([Word], Bool)
+ Haskoin.Util.Helpers: convertBits :: Bool -> Int -> Int -> [Word] -> Maybe [Word]
- Haskoin.Util.Marshal: unmarshalLazy :: Marshal s a => s -> ByteString -> a
+ Haskoin.Util.Marshal: unmarshalLazy :: Marshal s a => s -> ByteString -> Either String a

Files

CHANGELOG.md view
@@ -5,6 +5,135 @@ 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). +## [1.5.0] - 2026-11-03++### Added++- Taproot signing and verification, BIP 341 and BIP 342: `taprootSigHash` computes the digest a Taproot signature signs, for the key path and for a leaf of a script path; `taprootOutputSecKey` tweaks a secret key as `taprootOutputKey` tweaks its public key; `signTx` signs a `SigInput` of a Taproot output by its key path, with a key whose x-only public key is the output key or whose tweak without a script tree is, as a BIP 86 wallet spends, and `signTaprootInput` signs one input; `verifyStdTx` verifies key path spends, and script path spends whose leaf is a single key check. `TaprootSignature`, `encodeTaprootSig` and `decodeTaprootSig` are a Schnorr signature with its hash type, `isTaprootSigHash` tells the hash types a Taproot signature may use, and `sigHashDefault` is the default one. The key path vectors of BIP 341 and the BIP 86 vectors check them.+- The Taproot fields of PSBTs, BIP 371, in `Haskoin.Transaction.Psbt`: `tapKeySig`, `tapScriptSigs`, `tapLeafScripts`, `inputTapDerivations`, `inputTapInternalKey`, `inputTapMerkleRoot`, `outputTapInternalKey`, `outputTapTree` and `outputTapDerivations`, with their inserts. They are checked against the spec when a PSBT is read. `signPsbt` signs a Taproot coin by its key path when the input has its internal key, and by each leaf script that is a single key check; `finalizePsbt` finalizes both. The test vectors of BIP 371 check them.+- `tapTreeLeaves` and `tapTreeFromLeaves` in `Haskoin.Transaction.Taproot` convert a script tree to and from its leaves with their depth, the form of `PSBT_OUT_TAP_TREE`. `encodeControlBlock` and `decodeControlBlock` build and read a control block, which must be 33 bytes plus 32 per node with at most `maxControlBlockNodes` (128) nodes. `leafHash`, `keyParity` and `tapscriptLeafVersion` are exported.+- `txWitnessHash` in `Haskoin.Transaction.Common` is the witness transaction id of BIP 141. `txWeight` and `txVirtualSize` give the weight and the virtual size of a transaction, and `hasWitness` tells whether it has a witness.+- `InputKind` and `OutputKind` in `Haskoin.Transaction.Builder` name the kinds of inputs and outputs: pay-to-public-key, pay-to-public-key-hash with a compressed or an uncompressed key, pay-to-script-hash multisig, the native and nested SegWit kinds, Taproot, pay-to-script-hash of a 256-bit hash, and any other output script by its size. `guessTxWeight`, `guessTxSize` and `guessTxFee` estimate the weight, the virtual size and the fee of a transaction of those kinds, with `inputWeight` and `outputSize` per kind. The guess is an upper bound: it counts 72 bytes per signature, the one-byte empty witness stack of a legacy input in a transaction with a witness, and two bytes for each count of a multisig script above 16.+- `Signer` in `Haskoin.Transaction.Psbt` is a record: `keyOfRecord` gives the key a derivation record names, as before, and `keyOfHash` the key of a public key hash, so that `signPsbt` signs the coins whose keys it holds when the input has no derivation record. `keySigner` provides both, `xPrvSigner` the first.+- Tests for every function of the `Haskoin.Transaction` and `Haskoin.Util` modules, with the key path vectors of BIP 341, the vectors of BIP 86 and BIP 371, and literal sizes, ids and fee estimates.+- `connectBlock` and `connectBlocks` now check that a header carries the difficulty bits the chain requires, as `ContextualCheckBlockHeader` does in Bitcoin Core. `validBlock` takes the required bits as a new argument. Without this check a chain of headers mined at the minimum difficulty was accepted.+- `asertAnchor` finds the anchor block of the aserti3-2d algorithm for the chain that ends at a given block. `mtp` is now the median time past of the block itself, as in Bitcoin Core, instead of its parent's.+- `getVarIntList` and `putVarIntList` in `Haskoin.Network.Common` read and write lists with their `VarInt` count. A count above `maxListSize` (32 MiB, the `MAX_SIZE` of Bitcoin Core) fails the parse instead of wrapping around and reading an empty list. Blocks, Merkle blocks, `getblocks`, `getheaders`, `headers`, `inv`, `getdata`, `notfound` and `addr` messages use them.+- `genesisTx` in `Network` holds the coinbase transaction of the genesis block. `Haskoin.Transaction.Genesis` exports `mainnetGenesisTx`, `testnet4GenesisTx` and `genesisCoinbase`.+- `skipHeight` and `medianTime` are exported from `Haskoin.Block.Headers`.+- Tests for every function of the `Haskoin.Block` modules, with vectors from Bitcoin Core and from mainnet block 100000.++- PSBT version 2 (BIP 370) in `Haskoin.Transaction.Psbt`: a PSBT of version 2 is read and written as its maps, with the unsigned transaction its fields describe and the lock time they determine. The roles take both versions, and version 2 has its constructor (`addInput`, `addOutput`), its updater (`setInputSequence`), the flags of `PSBT_GLOBAL_TX_MODIFIABLE`, which the signer and the combiner keep as the spec and Bitcoin Core do, and conversion between the versions (`withVersion`). The test vectors of BIP 370 and files of Bitcoin Core 32.1 check it.+- `addrToBase58`, `addrToBech32` and `addrToCash` encode an address in one chosen format. `addrToBase58` gives the legacy form of a Bitcoin Cash address.+- Token-aware CashAddr addresses (types 2 and 3) can be paid. `addressToOutput`, `addressHash160` and `addressHash256` treat them like their plain P2PKH, P2SH and P2SH32 counterparts.+- `decodeSecKey` in `Haskoin.Crypto.Keys.Common` builds a secret key from 32 bytes and refuses zero and values at or above the curve order. `fromWif`, `fromMiniKey`, `xPrvImport`, `makeXPrvKey` and the binary form of `PrivateKey` use it. The `secKey` of secp256k1-haskell 1.7.0 only checks the length, so such keys used to import and then crash `derivePubKey` and `signMsg` with an error call.+- Derivation paths accept `h` and `H` as hardened markers, as Bitcoin Core does, so `m/84h/0h/0h` parses.+- Tests for BIP32 test vectors 4 and 5, for a BIP39 passphrase with accented letters, and for every function of the `Haskoin.Crypto` modules.+- The P2P messages added since 2016: `addrv2` and `sendaddrv2` (BIP 155) with `AddrV2`, `NetworkAddressV2` and conversions from and to `addr` entries; `feefilter` (BIP 133); `sendcmpct`, `cmpctblock`, `getblocktxn` and `blocktxn` (BIP 152) in `Haskoin.Network.CompactBlock`; `getcfilters`, `cfilter`, `getcfheaders`, `cfheaders`, `getcfcheckpt` and `cfcheckpt` (BIP 157) in `Haskoin.Network.BlockFilter`; `wtxidrelay` (BIP 339) and `sendtxrcncl` (BIP 330). The inventory types `InvCompactBlock` and `InvWTx`, and the service bits `nodeCompactFilters`, `nodeNetworkLimited` and `nodeP2PV2`.+- `maxMessageSize` in `Network` bounds the payload `getMessage` reads: 4,000,000 bytes on Bitcoin, as in Bitcoin Core, and twice the block size on Bitcoin Cash.+- `scriptPushData` lists the data elements a script pushes, as `GetOp` does in Bitcoin Core.+- `getVarIntBytes` and `putVarIntBytes` read and write bytes with their count, under the same limit as lists.+- Tests with fixed wire bytes for every message type, the sample `version`, `verack` and `addr` messages of the protocol documentation, and every function of the `Haskoin.Network` modules.+- `pushOpData` gives the data that a push opcode puts on the stack, and `isMinimalPush` tells whether a push is in its shortest encoding, the rule of `CheckMinimalPush` in Bitcoin Core.+- `OP_CHECKSIGADD` is a pattern synonym for opcode 0xba, which `OP_CHECKDATASIG` also names, so a Tapscript can be written with the name of BIP 342.+- `NestedWitnessInput` is the input script of a P2SH-P2WPKH or P2SH-P2WSH output: a push of the witness output script. `decodeInput`, `encodeInput`, `inputAddress` and the signer use it, and `isNestedWitnessInput` tests for it. Such an input used to fail to decode.+- `maxMultisigKeys` is 20, the `MAX_PUBKEYS_PER_MULTISIG` of Bitcoin Core. `decodeOutput` reads a multisig output with 17 to 20 keys, whose counts are one-byte pushes, and `encodeOutput` writes one. Only 16 keys used to be supported.+- `decodePubKey` in `Haskoin.Crypto.Keys.Common` reads a public key from bytes that must hold the key and nothing else.+- Tests for every function of the `Haskoin.Script` modules, with literal vectors from BIP 67, BIP 143, BIP 173 and BIP 341.++### Changed++- `makeSignature`, and with it `signTx`, `signInput` and `signPsbt`, refuse to sign a legacy input with `SIGHASH_SINGLE` when the input has no output at its index. The digest of that case is the constant 1, which commits to nothing, so the signature would spend any other coin of the key wherever the same case comes up. `txSigHash` still computes that digest, as a node must to verify such signatures, and `verifyStdInput` still accepts them. A SegWit input is signed in that case, since the BIP 143 digest commits to the coin.+- `Signer` has a new field, `acceptedSigHashes`: the sighash types it signs with. `keySigner` and `xPrvSigner` set it to `defaultSigHashes`, which are `SIGHASH_DEFAULT` and `SIGHASH_ALL`. `signPsbt` refuses an input that asks for another type, as BIP 174 asks of a signer: the other types leave part of the transaction unsigned, and the PSBT comes from someone else. A PSBT asking for `SIGHASH_NONE` or `SIGHASH_SINGLE` used to be signed as asked. The fork id flag is not compared, since the network decides it.+- `PrivateKey` and `XPrvKey` show their secret as `<redacted>`, and no longer have a `Read` instance; nor does `XKey`. The derived instances printed the key, and for `XPrvKey` the chain code, so a value in a log or an error message gave the coins away. `toWif` and `xPrvExport` write a key out.+- `prvSubKey`, `pubSubKey`, `hardSubKey` and `cycleIndex` throw `DerivationException` for a child index with the hard bit set, like every other failure of a derivation. They used to call `error`.+- A transaction with no inputs whose next byte, the flags of BIP 144, is neither 0x00 nor 0x01 fails to parse with "Unknown transaction optional data", as in Bitcoin Core. Such bytes used to parse as a legacy transaction with no inputs that did not serialize back to the same bytes. A `Tx` string literal must hold the transaction and nothing else, as `DecodeHexTx` requires in Bitcoin Core.+- The documentation of `decodeBase58` and `decodeBase58Check` says that their cost grows with the square of the length and that callers must bound untrusted input. The decoders of the library bound it already.+- `taprootOutputKey` and `taprootScriptOutput` tweak the x-only internal key, as BIP 341 defines the output key. An internal key with an odd y coordinate used to be tweaked as a full point, which gave an output key and an address no other wallet derives from that key, and that a BIP 341 signer could not spend.+- `viewTaprootWitness` reads a witness in the order of BIP 341: the annex last, then the control block, then the script, then the stack. It used to read the control block and the script in the other order, and so decoded no real script path witness. `encodeTaprootWitness` writes the annex only when there is one; it used to append an empty item, which made the witness invalid. The control block of a `ScriptPathData` is a list of digests, and a control block of a wrong length is refused. `KeyPathSpend` carries the annex of a key path spend.+- `signPsbt` applies the checks of the BIP 174 signer. A coin known from `PSBT_IN_WITNESS_UTXO` alone is signed only with a witness signature, since the legacy digest does not commit to the amount; the previous transaction is used first, and a witness output that is not its output is refused. A sighash type no node accepts, or whose fork id flag does not fit the network, is refused instead of signed. A redeem script that does not hash to the coin, or a witness script that does not hash to the witness program, is refused. An input whose coin script is not standard is left alone instead of failing the PSBT.+- `finalizePsbt` takes the `Network` and verifies every signature it uses, against the digest of the coin and the sighash type of the input when it has one, as `PSBTInputSignedAndVerified` does in Bitcoin Core. A signature that does not verify, is not a signature, or uses another sighash type is refused. It used to finalize any bytes stored as a signature.+- `mergeTxs` leaves an input that no transaction signed as it is. It used to crash with `Prelude.head: empty list`.+- `chooseCoins` takes the kinds of the outputs and a fee rate per virtual byte, and estimates the fee from the `coinInputKind` of each coin, a new method of `Coin`. `chooseMSCoins`, `chooseMSCoinsSink`, `guessMSTxFee` and `guessMSSize` are gone, since the kind of input lives on the coin. The old estimate counted every input as pay-to-public-key-hash and charged bytes rather than virtual bytes, so a SegWit wallet overpaid fees by about 60 %.+- `hexToTxHash`, and the `Read`, string and JSON forms of `TxHash` and `OutPoint`, refuse hex that is not exactly 32 bytes. Longer hex used to be accepted with its leading bytes dropped.+- Transaction parsing bounds the counts of inputs, outputs, witness stacks and witness items by `maxListSize`, as `ReadCompactSize` does in Bitcoin Core; a count of 2^64 - 1 used to wrap around and read as zero. A transaction with the witness marker but no witness item is refused ("Superfluous witness record"), as Bitcoin Core refuses it. A transaction whose witness stacks are all empty is serialized without the marker, and `hasWitness` is false for it.+- `verifyStdTx` and `verifyStdInput` take the coins of every input, matched by outpoint, so that Taproot inputs can be verified. A witness on an input whose coin is not a witness program is refused, as `VerifyScript` refuses it in Bitcoin Core, and `verifyStdTx` refuses outputs that spend more than the coins hold.+- `nosigTxHash` removes the witness as well as the input scripts, so that it is the same before and after signing a SegWit input.+- `getBits` raises an error for input shorter than the bits asked, `getList` and `getLengthBytes` refuse a count that does not fit an `Int`, and `unmarshalLazy` returns `Either String a` instead of throwing.+- `setInputSequence` refuses to change the sequence of an input that is signed, since every signature commits to it.+- `WitnessProgram`, `WitnessProgramPKH`, `MAST`, `TaprootWitness` and `ScriptPathData` have `Show` and `Eq` instances.+- The package requires secp256k1-haskell 2.0.0, whose `normalizeSig` is total. `verifyHashSig` normalises the signature with it, and `isCanonicalHalfOrder` is its `isLowS`.+- `buildMerkleRoot` returns `Either String MerkleRoot`. It fails for an empty list, which used to crash, and for a mutated tree in which two sibling nodes are identical (CVE-2012-2459). `extractMatches` rejects such a tree too, as `CPartialMerkleTree::TraverseAndExtract` does in Bitcoin Core, and reports why a partial tree is malformed.+- `buildMerkleRoot`, `buildPartialMerkle` and `calcHash` build the tree in linear time instead of quadratic. `decodeMerkleFlags` is linear too; it took a minute for 100 KB of flags, which a peer could send in a `merkleblock` message.+- `genesisBlock` no longer takes a `Ctx` and returns the right coinbase for every network. Bitcoin testnet4 has its own genesis coinbase, so `genesisBlock btcTest4` used to return a block whose transactions did not match its header.+- The `merkleroot` field of the JSON form of `BlockHeader` is in display order, like `prevblock` and the `getblockheader` RPC call of Bitcoin Core. It was in internal byte order.+- `hexToBlockHash` and the JSON parser of `BlockHeader` reject a hash that is not exactly 32 bytes. They used to accept longer input and drop the leading bytes.+- `computeAsertBits` takes the proof-of-work limit of the network as its first argument instead of using the mainnet limit.+- `connectBlocks` adds each header to the store as it passes, so a batch with a bad header keeps the headers before it and makes the last of them the best block when it has the most work.+- `Ord BlockNode` compares the height and then the header, so that it agrees with `Eq`, which compares the header.+- `appendBlocks` dates every block one target spacing after its parent, so the chains it mines pass the median time past rule.+- `blockLocatorNodes` steps back like `LocatorEntries` in Bitcoin Core and lists the genesis block once.+- `encodeCompact` raises an error for a number wider than 255 bytes instead of wrapping the exponent.+- `traverseAndBuild` and `traverseAndExtract` are no longer exported.+- `getMessage` consumes exactly one message. It used to leave the payload in the stream and let the payload parser read past the declared length, so bytes outside the checksum could end up in a message. It now checks the magic before reading the payload, refuses a payload above `maxMessageSize`, and accepts a payload on message types that carry none, as Bitcoin Core does.+- `VarInt` refuses an encoding longer than the value needs, as `ReadCompactSize` does in Bitcoin Core. A transaction with such an encoding used to parse and re-serialize to different bytes. `VarString` refuses a count above `maxListSize`.+- `MessageCommand` refuses a name with a byte outside printable ASCII or with a byte other than NUL after it, as Bitcoin Core does, and serializing a name longer than twelve bytes is an error instead of a silent truncation.+- The `extra` field of `Reject` is a `Maybe Hash256`. It was a `ByteString` that only round-tripped when empty or 32 bytes long.+- `Version` refuses a user agent longer than 256 bytes, as Bitcoin Core does.+- `BloomFilter` refuses a filter above 36000 bytes or 50 hash functions when parsing, as Bitcoin Core refuses such a `filterload`, and reads its bytes in one go instead of one at a time. `FilterAdd` refuses an element above 520 bytes. `BloomFlags` reads only the two low bits of its byte, as Bitcoin Core does.+- `bloomRelevantUpdate` matches the data pushed by every output script, standard or not, and inserts outpoints according to the flags, as `IsRelevantAndUpdate` does in Bitcoin Core. It used to raise an error for any transaction with a non-standard output, and for a matching output that was not pay-to-pubkey or pay-to-multisig under `BloomUpdateP2PubKeyOnly`. It returns `Just` the filter whenever an output matches, unchanged under `BloomUpdateNone`.+- `bloomInsert` and `bloomContains` treat a filter with zero hash functions as matching everything, as Bitcoin Core does. `bloomInsert` used to loop over 2^32 hash functions and `bloomContains` used to answer `False`. `bloomCreate` gives zero hash functions for zero elements.+- `bip34Block` in `Network` is split into `bip34Height` and `bip34Hash`, the hash being optional. `btcRegTest` activates BIP34, BIP65 and BIP66 at height 1, as Bitcoin Core regtest does; it had the heights of Bitcoin Cash Node regtest. `bchRegTest` has the 32 MB block size of Bitcoin Cash Node.+- `arbitraryBloomFilter` generates a `BloomFilter` with random bits instead of a tuple.+- Replace the PSBT module with `Haskoin.Transaction.Psbt`, which keeps every field of a PSBT, known or not, writes maps with their keys in order as Bitcoin Core does, refuses what breaks BIP 174 (duplicate keys, keys out of shape, bytes after the last map, other versions), and reads and writes the global xpub and proprietary fields. It still creates, combines, signs, finalizes and extracts. The `Haskoin.Transaction.Partial` module and its types are gone.+- `pubKeyWitnessAddr` and `pubKeyCompatWitnessAddr` now return `Maybe Address` and refuse uncompressed keys. BIP 143 makes witness spends with uncompressed keys non-standard, so coins sent to such an address could not be spent through the normal network.+- The `WitnessAddress` and `CashAddress` constructors are now patterns that can only match. Build these addresses with `witnessAddress` and `cashAddress`, which validate the version and payload and return the dedicated constructor when one exists. Every address now has a single representation, so equal addresses compare equal.+- `outputAddress` and `scriptToAddress` no longer report the P2PKH address of the key for a pay-to-public-key output. Such coins cannot be spent with the P2PKH input script, so the address was misleading.+- `convertBits` in `Haskoin.Util.Helpers` returns `Maybe [Word]` and fails on padding bits that are not zero, or that are wide enough to hold a whole input digit, when padding is not allowed.+- JSON encoding of an address that has no text form on the given network now fails with an error instead of writing `null`.+- `mnemonicToSeed` NFKD-normalises the sentence and the passphrase, as BIP39 requires and as Trezor, Electrum, python-mnemonic and bitcoinjs do. A passphrase with accented letters used to give a seed that no other wallet derives, so the funds could not be restored elsewhere. `fromMnemonic` normalises the sentence before looking up its words. This adds a dependency on `unicode-transforms`.+- `toMnemonic` accepts 16 to 32 bytes of entropy and `fromMnemonic` accepts sentences of 12, 15, 18, 21 or 24 words, the sizes BIP39 allows. Entropy of 4 to 12 bytes and of 36 to 64 bytes, and sentences of 3 to 9 and 27 to 48 words, were accepted before, and no other wallet reads them. A word that is not in the list is reported with the `fromMnemonic:` prefix.+- `xPrvImport` and `xPubImport` require a payload of exactly 78 bytes, as Bitcoin Core does. Trailing bytes and an uncompressed public key used to be accepted. They also refuse a key at depth 0 whose parent fingerprint or index is not zero, as BIP32 test vector 5 requires.+- `fromWif` refuses strings longer than 52 characters, and `xPrvImport` and `xPubImport` strings longer than 112, before decoding them. No valid key is longer, and the time to decode Base58 grows with the square of the length.+- `prvSubKey`, `pubSubKey` and `hardSubKey` throw `DerivationException` when the parent is at depth 255. The depth of the child used to wrap around to 0.+- Parsing a derivation path reads each index as an unbounded integer and accepts decimal digits only. `m/4294967296` used to wrap around to `m/0`, and hex, parenthesised and signed numbers were accepted.+- The JSON form of `Sig` is decoded with `decodeStrictSig`, like the binary form, so a high-S signature is refused by both.+- The `Read` instances of `Hash160`, `Hash256` and `Hash512`, and `textToFingerprint`, refuse hex of the wrong length, as the string literal instances already did. A two-byte hash could be read and then crash `signHash`.+- `encodeOutput` returns `Either String Script`. It fails for a `PayMulSig` whose required count is not between 1 and its number of keys or that has more than 20 keys, and for a `PayWitness` whose version is 0 or above 16 or whose program is not 2 to 40 bytes long. Those values used to crash with an `error` call. `marshalPut` still fails with an `error` call for them, since it cannot report a failure.+- `txSigHash` and `txSigHashForkId` return `Either String Hash256`. They fail for an input index the transaction does not have. With `SIGHASH_ALL`, `txSigHash` used to return a digest over the transaction without any script code for such an index, and crash for the other hash types. `makeSignature` and `makeSigHash` return `Either` too.+- On a network with a fork id, `txSigHash` requires the `SIGHASH_FORKID` flag and `decodeTxSig` rejects a signature without it. `makeSignature` sets the flag, so `signTx bch` with a plain `sigHashAll` produces a signature the network accepts. It used to sign the legacy digest, which Bitcoin Cash has rejected since 2017.+- `isSigHashUnknown` is the test of `IsDefinedHashtypeSignature` in Bitcoin Core: after masking the `SIGHASH_ANYONECANPAY` and `SIGHASH_FORKID` bits, the hash type byte must be 1, 2 or 3. A byte such as 0x21 used to pass and be signed, although every node rejects it under `STRICTENC`.+- `opPushData` gives the minimal encoding of BIP 62: `OP_0` for the empty string, `OP_1` to `OP_16` for one byte from 1 to 16 and `OP_1NEGATE` for the byte 0x81. The empty push used to be an `OP_PUSHDATA` that serialized to the byte of `OP_0` but was not equal to it.+- `DataCarrier` holds the data of every push after `OP_RETURN`, as a list, since `TxoutType::NULL_DATA` of Bitcoin Core is `OP_RETURN` followed by any pushes. A bare `OP_RETURN`, several pushes and an empty push used to be "Non-standard output".+- `intToScriptOp` returns `Either String ScriptOp` instead of crashing outside 1 to 16. `scriptOpToInt` no longer serializes its argument, so it cannot crash on an `OP_PUSHDATA` whose payload does not fit its type.+- `sortMulSig` returns any output that is not a multisig unchanged instead of crashing.+- `decodeInput` reads the input of a pay-to-script-hash output as its redeem script requires. A multisig template with the redeem script and no signature yet, `OP_0 <redeem>`, is a `SpendMulSig []`, which `mergeTxs` used to reject. An input that does not fit its redeem script fails.+- The generators of `Haskoin.Util.Arbitrary.Script` produce pushes of every `PushDataType`, pushes that are not minimal, multisig outputs of up to 20 keys, data carriers with any number of pushes, nested witness inputs, and, on networks with a fork id, only hash types with the flag.++### Fixed++- A block whose timestamp equals the median time past of its parent is rejected, as in Bitcoin Core. It used to be accepted.+- `connectBlocks` keeps the block that was seen first as the best block when a competing block has the same work and height, as `connectBlock` and Bitcoin Core do. It used to switch the best block to every competitor.+- `calcNextWork` and `computeTarget` measure the time span of a retarget period as a signed number. A period whose last block is dated before its first block used to wrap around to four times the target timespan instead of being limited to a quarter.+- `headerWork` returns zero for bits that are negative, overflow or encode a zero target, like `GetBlockProof` in Bitcoin Core. It used to divide by zero for bits `0x03800001`.+- The block version is compared as a signed number, as in Bitcoin Core, so versions with the top bit set are rejected once BIP34 is active.+- The Bitcoin Cash difficulty algorithms follow Bitcoin Cash Node: the minimum-difficulty rule of test networks applies to the ASERT algorithm and no longer applies to the EDA and DAA algorithms on mainnet, the EDA returns the bits of the last block without minimum difficulty on test networks, `getSuitableBlock` picks the same block as Bitcoin Cash Node when timestamps are equal, `computeTarget` no longer overshoots by one, the ASERT anchor is the first block whose own median time past reaches the activation time, and regtest never retargets.+- A PSBT of another version is refused by its version. The unsigned transaction was looked for first, and a PSBT of version 2 holds none.+- `addressBytes` returned a length-prefixed value for a generic CashAddr instead of the raw hash.+- Base58, Bech32 and CashAddr decoders accepted non-ASCII look-alike characters. Base58 truncated code points to one byte and Bech32 used Unicode case folding. All three now accept ASCII only, as the specifications require.+- `cashAddrDecode` accepted mixed-case strings, which the CashAddr specification rejects.+- CashAddr decoding accepted non-canonical strings whose padding bits were not zero, or that carried an extra group of padding, so several strings could stand for the same address.+- `textToAddr` decoded Base58 input of any length, with a cost growing with the square of the length. Strings longer than any valid address are now rejected first.+- Binary deserialization of an address now validates the witness version, the CashAddr type and the payload length, and checks the length prefix before converting it to an `Int`. Previously an invalid value could be read and later crash `addressToScript`.+- The Bech32 and bit conversion accumulators are masked so that they cannot overflow on long inputs.+- `encodeOutput` had no case for `PayScript32Hash` and crashed on it, so a P2SH32 address could not be turned into a script.+- The P2WSH test generator produced P2WPKH addresses.+- `fromWif` threw an exception on a Base58Check string with an empty payload, such as `3QJmnh`, also through the JSON parser of `PrivateKey`. It returns `Nothing` now.+- The JSON parser of `PublicKey` accepted bytes after the key.+- `decodeOutput` matches the output templates on their exact bytes, as Bitcoin Core does. A script that wrote the hash of a P2PKH, P2SH, P2SH32 or witness output with `OP_PUSHDATA1` or a longer push used to decode as that output and give its address. By consensus such a script is not a P2SH or witness output at all, and anyone can spend it, so a wallet could credit a payment to its address that was not one.+- A public key pushed in a script or a witness must fill its push. A 65-byte item that starts with 0x02 or 0x03 used to parse as the 33-byte key it starts with, so `decodeOutput`, `decodeInput` and the witness decoders reported a key the script did not hold, and `verifyStdTx` accepted a P2PKH or P2WPKH spend whose key item carried 32 extra bytes, which no node accepts.+- `verifyStdInput` returns `False` for an input index the transaction does not have, and `parseExistingSigs` reads no witness signatures when the witness list is shorter than the input index. Both used to crash.+ ## [1.4.0] - 2026-10-31  ### Added
+ data/block_100000.hex view
@@ -0,0 +1,1 @@+0100000050120119172a610421a6c3011dd330d9df07b63616c2cc1f1cd00200000000006657a9252aacd5c0b2940996ecff952228c3067cc38d4885efb5a4ac4247e9f337221b4d4c86041b0f2b57100401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84ac000000000100000001032e38e9c0a84c6046d687d10556dcacc41d275ec55fc00779ac88fdf357a187000000008c493046022100c352d3dd993a981beba4a63ad15c209275ca9470abfcd57da93b58e4eb5dce82022100840792bc1f456062819f15d33ee7055cf7b5ee1af1ebcc6028d9cdb1c3af7748014104f46db5e9d61a9dc27b8d64ad23e7383a4e6ca164593c2527c038c0857eb67ee8e825dca65046b82c9331586c82e0fd1f633f25f87c161bc6f8a630121df2b3d3ffffffff0200e32321000000001976a914c398efa9c392ba6013c5e04ee729755ef7f58b3288ac000fe208010000001976a914948c765a6914d43f2a7ac177da2c2f6b52de3d7c88ac000000000100000001c33ebff2a709f13d9f9a7569ab16a32786af7d7e2de09265e41c61d078294ecf010000008a4730440220032d30df5ee6f57fa46cddb5eb8d0d9fe8de6b342d27942ae90a3231e0ba333e02203deee8060fdc70230a7f5b4ad7d7bc3e628cbe219a886b84269eaeb81e26b4fe014104ae31c31bf91278d99b8377a35bbce5b27d9fff15456839e919453fc7b3f721f0ba403ff96c9deeb680e5fd341c0fc3a7b90da4631ee39560639db462e9cb850fffffffff0240420f00000000001976a914b0dcbf97eabf4404e31d952477ce822dadbe7e1088acc060d211000000001976a9146b1281eec25ab4e1e0793ff4e08ab1abb3409cd988ac0000000001000000010b6072b386d4a773235237f64c1126ac3b240c84b917a3909ba1c43ded5f51f4000000008c493046022100bb1ad26df930a51cce110cf44f7a48c3c561fd977500b1ae5d6b6fd13d0b3f4a022100c5b42951acedff14abba2736fd574bdb465f3e6f8da12e2c5303954aca7f78f3014104a7135bfe824c97ecc01ec7d7e336185c81e2aa2c41ab175407c09484ce9694b44953fcb751206564a9c24dd094d42fdbfdd5aad3e063ce6af4cfaaea4ea14fbbffffffff0140420f00000000001976a91439aa3d569e06a1d7926dc4be1193c99bf2eb9ee088ac00000000
+ data/psbt_bip174.json view
@@ -0,0 +1,236 @@+{+ "description": "The test vectors of BIP 174: the invalid PSBTs, which hw must refuse, the valid ones, which hw must read and write back byte for byte, and the PSBTs of the walk-through of the roles (creator, updater, signer, combiner, finalizer). Copied from the BIP, which is BSD-2-Clause.",+ "source": "https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki",+ "invalid": [+  {+   "case": "Network transaction, not PSBT format",+   "hex": "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300",+   "base64": "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA=="+  },+  {+   "case": "PSBT missing outputs",+   "hex": "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",+   "base64": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA=="+  },+  {+   "case": "PSBT where one input has a filled scriptSig in the unsigned tx",+   "hex": "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000",+   "base64": "cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA="+  },+  {+   "case": "PSBT where inputs and outputs are provided but without an unsigned tx",+   "hex": "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",+   "base64": "cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA=="+  },+  {+   "case": "PSBT with duplicate keys in an input",+   "hex": "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000",+   "base64": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA"+  },+  {+   "case": "PSBT with invalid global transaction typed key",+   "hex": "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8CAAFVAgAAAAEnmiMjpd+1H8RfIg+liw/BPh4zQnkqhdfjbNYzO1y8OQAAAAAA/////wGgWuoLAAAAABl2qRT/6cAGEJfMO2NvLLBGD6T8Qn0rRYisAAAAAAABASCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA"+  },+  {+   "case": "PSBT with invalid input witness utxo typed key",+   "hex": "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAIBACCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA"+  },+  {+   "case": "PSBT with invalid pubkey length for input partial signature typed key",+   "hex": "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIQIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYwQwIgBCS1jv+qppThVZ6lyTu/1KiQZCJAVc3wcLZ3FGlELQcCH1yOsP6mUW1guKyzOtZO3mDoeFv7OqlLmb34YVHbmpoBAQQiACB3H9GK1FlmbdSfPVZOPbxC9MhHdONgraFoFqjtSI1WgQEFR1IhA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GIQPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvVKuIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA=="+  },+  {+   "case": "PSBT with invalid redeemscript typed key",+   "hex": "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQIEACIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA"+  },+  {+   "case": "PSBT with invalid witnessscript typed key",+   "hex": "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoECBQBHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA"+  },+  {+   "case": "PSBT with invalid pubkey in input BIP 32 derivation paths typed key",+   "hex": "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriEGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb0QtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA=="+  },+  {+   "case": "PSBT with invalid non-witness utxo typed key",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAIAALsCAAAAAarXOTEBi9JfhK5AC2iEi+CdtwbqwqwYKYur7nGrZW+LAAAAAEhHMEQCIFj2/HxqM+GzFUjUgcgmwBW9MBNarULNZ3kNq2bSrSQ7AiBKHO0mBMZzW2OT5bQWkd14sA8MWUL7n3UYVvqpOBV9ugH+////AoDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSH0PIKJwEAAAAXqRQpynT4oI+BmZQoGFyXtdhS5AY/YYdlAAAAAQfaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="+  },+  {+   "case": "PSBT with invalid final scriptsig typed key",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAACBwDaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="+  },+  {+   "case": "PSBT with invalid final script witness typed key",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAggA2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="+  },+  {+   "case": "PSBT with invalid pubkey in output BIP 32 derivation paths typed key",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA"+  },+  {+   "case": "PSBT with invalid input sighash type typed key",+   "hex": "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",+   "base64": "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A"+  },+  {+   "case": "PSBT with invalid output redeemScript typed key",+   "hex": "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",+   "base64": "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A"+  },+  {+   "case": "PSBT with invalid output witnessScript typed key",+   "hex": "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d06d57f8a8751ae00",+   "base64": "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnQbVf4qHUa4A"+  },+  {+   "case": "PSBT with unsigned tx serialized with witness serialization format",+   "hex": "70736274ff01007802000000000101268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc78700b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000",+   "base64": "cHNidP8BAHgCAAAAAAEBJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAAP7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHALMuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA"+  },+  {+   "case": "PSBT with an invalid value data due to its size being not the stated size",+   "hex": "70736274ff0100337401ff0700010000000100ff01000a73317428ff0000000001ff010301000001000000000000000076010000004100090000000000",+   "base64": "cHNidP8BADN0Af8HAAEAAAABAP8BAApzMXQo/wAAAAAB/wEDAQAAAQAAAAAAAAAAdgEAAABBAAkAAAAAAA=="+  }+ ],+ "valid": [+  {+   "case": "PSBT with one P2PKH input. Outputs are empty",+   "hex": "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000",+   "base64": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA"+  },+  {+   "case": "PSBT with one P2PKH input and one P2SH-P2WPKH input. First input is signed and finalized. Outputs are empty",+   "hex": "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000",+   "base64": "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA"+  },+  {+   "case": "PSBT with one P2PKH input which has a non-final scriptSig and has a sighash type specified. Outputs are empty",+   "hex": "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000",+   "base64": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA=="+  },+  {+   "case": "PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled.",+   "hex": "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000",+   "base64": "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="+  },+  {+   "case": "PSBT with one P2SH-P2WSH input of a 2-of-2 multisig, redeemScript, witnessScript, and keypaths are available. Contains one signature.",+   "hex": "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+   "base64": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA="+  },+  {+   "case": "PSBT with one P2WSH input of a 2-of-2 multisig. witnessScript, keypaths, and global xpubs are available. Contains no signatures. Outputs filled.",+   "hex": "70736274ff01005202000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef133579000000004f01043587cf02da3fd0088000000097048b1ad0445b1ec8275517727c87b4e4ebc18a203ffa0f94c01566bd38e9000351b743887ee1d40dc32a6043724f2d6459b3b5a4d73daec8fbae0472f3bc43e20cd90c6a4fae000080000000804f01043587cf02da3fd00880000001b90452427139cd78c2cff2444be353cd58605e3e513285e528b407fae3f6173503d30a5e97c8adbc557dac2ad9a7e39c1722ebac69e668b6f2667cc1d671c83cab0cd90c6a4fae000080010000800001012b0065cd1d000000002200202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa6320105475221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae2206029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c887110d90c6a4fae0000800000008000000000220603372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b10d90c6a4fae0000800100008000000000002202039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef910ede45cc500000080000000800100008000",+   "base64": "cHNidP8BAFICAAAAAZ38ZijCbFiZ/hvT3DOGZb/VXXraEPYiCXPfLTht7BJ2AQAAAAD/////AfA9zR0AAAAAFgAUezoAv9wU0neVwrdJAdCdpu8TNXkAAAAATwEENYfPAto/0AiAAAAAlwSLGtBEWx7IJ1UXcnyHtOTrwYogP/oPlMAVZr046QADUbdDiH7h1A3DKmBDck8tZFmztaTXPa7I+64EcvO8Q+IM2QxqT64AAIAAAACATwEENYfPAto/0AiAAAABuQRSQnE5zXjCz/JES+NTzVhgXj5RMoXlKLQH+uP2FzUD0wpel8itvFV9rCrZp+OcFyLrrGnmaLbyZnzB1nHIPKsM2QxqT64AAIABAACAAAEBKwBlzR0AAAAAIgAgLFSGEmxJeAeagU4TcV1l82RZ5NbMre0mbQUIZFuvpjIBBUdSIQKdoSzbWyNWkrkVNq/v5ckcOrlHPY5DtTODarRWKZyIcSEDNys0I07Xz5wf6l0F1EFVeSe+lUKxYusC4ass6AIkwAtSriIGAp2hLNtbI1aSuRU2r+/lyRw6uUc9jkO1M4NqtFYpnIhxENkMak+uAACAAAAAgAAAAAAiBgM3KzQjTtfPnB/qXQXUQVV5J76VQrFi6wLhqyzoAiTACxDZDGpPrgAAgAEAAIAAAAAAACICA57/H1R6HV+S36K6evaslxpL0DukpzSwMVaiVritOh75EO3kXMUAAACAAAAAgAEAAIAA"+  },+  {+   "case": "PSBT with unknown types in the inputs.",+   "hex": "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000af00102030405060708090f0102030405060708090a0b0c0d0e0f0000",+   "base64": "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA="+  },+  {+   "case": "PSBT with `PSBT_GLOBAL_XPUB`.",+   "hex": "70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c503100008000000080000000800001011f00e1f5050000000016001433b982f91b28f160c920b4ab95e58ce50dda3a4a220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c5031000080000000800000008000000000010000000001011f00e1f50500000000160014388fb944307eb77ef45197d0b0b245e079f011de220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000",+   "base64": "cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA"+  },+  {+   "case": "PSBT with global unsigned tx that has 0 inputs and 0 outputs",+   "hex": "70736274ff01000a0000000000000000000000",+   "base64": "cHNidP8BAAoAAAAAAAAAAAAAAA=="+  },+  {+   "case": "PSBT with 0 inputs",+   "hex": "70736274ff01004c020000000002d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000000",+   "base64": "cHNidP8BAEwCAAAAAALT3/UFAAAAABl2qRTQxZkDxbrChodg6Q/VIaRmWqdlIIisAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4ezLhMAAAAA"+  },+  {+   "case": "A Witness UTXO is provided for a non-witness input",+   "hex": "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac0000000000010122d3dff505000000001976a914d48ed3110b94014cb114bd32d6f4d066dc74256b88ac0001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000",+   "base64": "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEBItPf9QUAAAAAGXapFNSO0xELlAFMsRS9Mtb00GbcdCVriKwAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="+  },+  {+   "case": "redeemScript with non-witness UTXO does not match the scriptPubKey",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752af2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq8iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="+  },+  {+   "case": "redeemScript with witness UTXO does not match the scriptPubKey",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028900010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="+  },+  {+   "case": "witnessScript with witness UTXO does not match the redeemScript",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ad2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+   "base64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrSIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="+  }+ ],+ "roles": [+  {+   "case": "The creator's PSBT",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"+  },+  {+   "case": "The updater's PSBT, with the previous transactions, scripts and derivations",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "The updater's PSBT with SIGHASH_ALL on every input",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "The PSBT signed with the keys m/0'/0'/0' and m/0'/0'/2'",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "The PSBT signed with the keys m/0'/0'/1' and m/0'/0'/3'",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "The combiner's PSBT of the two signed ones",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "The finalizer's PSBT",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "A PSBT with unknown key-value pairs, the first",+   "hex": "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000af00102030405060708090f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f00"+  },+  {+   "case": "A PSBT with unknown key-value pairs, the second",+   "hex": "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708100f0102030405060708090a0b0c0d0e0f00"+  },+  {+   "case": "The combiner's PSBT of the two with unknown pairs, its keys in lexicographic order",+   "hex": "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000af00102030405060708090f0102030405060708090a0b0c0d0e0f0af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f0af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f0af00102030405060708100f0102030405060708090a0b0c0d0e0f00"+  },+  {+   "case": "A PSBT to combine, the first",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "A PSBT to combine, the second",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022038bf34353f6c65bd4043eab7eac2fdd3c14cc7b62dad6c28bfa9989526ab375a0220097bcc5719f9aa7e3354a69f6839631b1c594f88c8bb119dcda68ec310159cec81010304810000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022001cb78fed0c015af295785eddd0fcf7f2145d1f580164820e61cb568f8fe843f02205bcf90d39fc9224477422e9c2f96aca8f566915cefa4ecd4e5c52a99b5889291810103048100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  },+  {+   "case": "The combiner's PSBT of those two",+   "hex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022038bf34353f6c65bd4043eab7eac2fdd3c14cc7b62dad6c28bfa9989526ab375a0220097bcc5719f9aa7e3354a69f6839631b1c594f88c8bb119dcda68ec310159cec81010304810000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022001cb78fed0c015af295785eddd0fcf7f2145d1f580164820e61cb568f8fe843f02205bcf90d39fc9224477422e9c2f96aca8f566915cefa4ecd4e5c52a99b5889291810103048100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"+  }+ ],+ "extracted": {+  "case": "The transaction the extractor gives for the finalizer's PSBT",+  "hex": "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000"+ }+}
+ data/psbt_bip370.json view
@@ -0,0 +1,258 @@+{+ "invalid": [+  {+   "case": "PSBTv0 but with PSBT_GLOBAL_VERSION set to 2",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_GLOBAL_TX_VERSION",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001020402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAECBAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_GLOBAL_FALLBACK_LOCKTIME",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001030402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEDBAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_GLOBAL_INPUT_COUNT",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001040102000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEEAQIAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_GLOBAL_OUTPUT_COUNT",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001050102000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEFAQIAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_GLOBAL_TX_MODIFIABLE",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001060100000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEGAQAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_IN_PREVIOUS_TXID",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a27010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc800220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAACICA27+LCVWIZhlU7qdZcPdxkFlyhQ24FqjWkxusCRRz3ltGPadhz5UAACAAQAAgAAAAIABAAAAYgAAAAA="+  },+  {+   "case": "PSBTv0 but with PSBT_IN_OUTPUT_INDEX",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a27010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_IN_SEQUENCE",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a27011004ffffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonARAE/////wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_IN_REQUIRED_TIME_LOCKTIME",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a270111048c8dc46200220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAREEjI3EYgAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a270112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv0 but with PSBT_OUT_AMOUNT",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f00000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAACICA27+LCVWIZhlU7qdZcPdxkFlyhQ24FqjWkxusCRRz3ltGPadhz5UAACAAQAAgAAAAIABAAAAYgAAAAA="+  },+  {+   "case": "PSBTv0 but with PSBT_OUT_SCRIPT",+   "hex": "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000104160014a07dac8ab6ca942d379ed795f835ba71c9cc6885002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000",+   "base64": "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEEFgAUoH2sirbKlC03nteV+DW6ccnMaIUAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="+  },+  {+   "case": "PSBTv2 but with PSBT_GLOBAL_UNSIGNED_TX",+   "hex": "70736274ff0100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e00000000010204020000000103040000000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQIEAgAAAAEDBAAAAAABBAEBAQUBAgEGAQcB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wERBIyNxGIBEgQQJwAAACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"+  },+  {+   "case": "PSBTv2 missing PSBT_GLOBAL_INPUT_COUNT",+   "hex": "70736274ff01020402000000010304000000000105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "PSBTv2 missing PSBT_GLOBAL_OUTPUT_COUNT",+   "hex": "70736274ff01020402000000010304000000000104010101fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "PSBTv2 missing PSBT_GLOBAL_TX_VERSION",+   "hex": "70736274ff010401010105010201fb040200000000010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BBAEBAQUBAgH7BAIAAAAAAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"+  },+  {+   "case": "PSBTv2 missing PSBT_IN_PREVIOUS_TXID",+   "hex": "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEPBAAAAAABEAT+////ACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"+  },+  {+   "case": "PSBTv2 missing PSBT_IN_OUTPUT_INDEX",+   "hex": "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "PSBTv2 missing PSBT_OUT_AMOUNT",+   "hex": "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8AIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"+  },+  {+   "case": "PSBTv2 missing PSBT_OUT_SCRIPT",+   "hex": "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f0000000000220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8AIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAAAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "PSBTv2 with PSBT_IN_REQUIRED_TIME_LOCKTIME less than 500000000",+   "hex": "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011104ff64cd1d00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAAREE/2TNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "PSBTv2 with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME greater than or equal to 500000000",+   "hex": "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f04000000000112040065cd1d00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARIEAGXNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "PSBTv2 with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 0",+   "hex": "70736274ff010204020000000103040000000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAQYBBwH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BDiALCtkhQZwchxlzXXLcc5+eqeBjjR/kwe7w+ZRAhIFfyAEPBAAAAAABEAT+////AREEjI3EYgESBAAAAAAAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAABBBYAFMQw9kxHVtoxDb0aCFVy7ymZJicsACICAuNvv/U91TQHDPj9OWYUaA81epuF23NAvxz6dF0q17NAGPadhz5UAACAAQAAgAAAAIABAAAAZAAAAAEDCIu96wsAAAAAAQQWABRN0ZOslkpWrBueHMqEVP4vR0+FEwA="+  }+ ],+ "valid": [+  {+   "case": "1 input, 2 output PSBTv2, required fields only",+   "hex": "70736274ff01020402000000010401010105010201fb040200000000010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2",+   "hex": "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with PSBT_IN_SEQUENCE",+   "hex": "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with PSBT_IN_SEQUENCE, and all locktime fields",+   "hex": "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8BEQSMjcRiARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with Inputs Modifiable Flag (bit 0) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010101fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEBAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with Outputs Modifiable Flag (bit 1) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with Has SIGHASH_SINGLE Flag (bit 2) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010401fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEEAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with an undefined flag (bit 3) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010801fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEIAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with both Inputs Modifiable Flag (bit 0) and Outputs Modifiable Flag (bit 1) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010301fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEDAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with both Inputs Modifiable Flag (bit 0) and Has SIGHASH_SINGLE Flag (bit 2) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010501fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEFAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with both Outputs Modifiable Flag (bit 1) and Has SIGHASH_SINGLE FLag (bit 2) of PSBT_GLOBAL_TX_MODIFIABLE set",+   "hex": "70736274ff0102040200000001040101010501020106010601fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEGAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with all defined PSBT_GLOBAL_TX_MODIFIABLE flags set",+   "hex": "70736274ff0102040200000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEHAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with all possible PSBT_GLOBAL_TX_MODIFIABLE flags set",+   "hex": "70736274ff010204020000000104010101050102010601ff01fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgH/AfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "1 input, 2 output updated PSBTv2, with all PSBTv2 fields",+   "hex": "70736274ff010204020000000103040000000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAQYBBwH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BDiALCtkhQZwchxlzXXLcc5+eqeBjjR/kwe7w+ZRAhIFfyAEPBAAAAAABEAT+////AREEjI3EYgESBBAnAAAAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAABBBYAFMQw9kxHVtoxDb0aCFVy7ymZJicsACICAuNvv/U91TQHDPj9OWYUaA81epuF23NAvxz6dF0q17NAGPadhz5UAACAAQAAgAAAAIABAAAAZAAAAAEDCIu96wsAAAAAAQQWABRN0ZOslkpWrBueHMqEVP4vR0+FEwA="+  }+ ],+ "locktime": [+  {+   "case": "No locktimes specified",+   "hex": "70736274ff01020402000000010401010105010201fb040200000000010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300",+   "locktime": 0,+   "base64": "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="+  },+  {+   "case": "Fallback locktime of 0",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f040100000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f0400000000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 0,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has no locktime fields",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f0400000000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 10000,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA"+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f040000000001120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 10000,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAESBCgjAAAAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA=="+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc46201120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 10000,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048459, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000111048b8dc4620112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc46201120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 10000,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiARIEKCMAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA"+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048459, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000111048b8dc46200010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc46201120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 1657048460,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048459, Input 2 has PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000111048b8dc4620112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc462000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 1657048460,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="+  },+  {+   "case": "Input 1 has no locktime fields, Input 2 has PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f040100000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc462000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": 1657048460,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAREEjI3EYgABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA"+  },+  {+   "case": "Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460",+   "hex": "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc462000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100",+   "locktime": null,+   "base64": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA=="+  }+ ]+}
+ data/psbt_bip371.json view
@@ -0,0 +1,93 @@+{+  "description": "Test vectors of BIP 371, Taproot fields in PSBT",+  "source": "https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki",+  "invalid": [+    {+      "case": "PSBT With PSBT_IN_TAP_INTERNAL_KEY key that is too long (incorrectly serialized as compressed DER)",+      "hex": "70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a075701172102fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232000000",+      "base64": "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARchAv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyAAAA"+    },+    {+      "case": "PSBT With PSBT_IN_TAP_KEY_SIG signature that is too short",+      "hex": "70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a075701133f173bb3d36c074afb716fec6307a069a2e450b995f3c82785945ab8df0e24260dcd703b0cbf34de399184a9481ac2b3586db6601f026a77f7e4938481bc3475000000",+      "base64": "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARM/Fzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1AAAA"+    },+    {+      "case": "PSBT With PSBT_IN_TAP_KEY_SIG signature that is too long",+      "hex": "70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757011342173bb3d36c074afb716fec6307a069a2e450b995f3c82785945ab8df0e24260dcd703b0cbf34de399184a9481ac2b3586db6601f026a77f7e4938481bc34751701aa000000",+      "base64": "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARNCFzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1FwGqAAAA"+    },+    {+      "case": "PSBT With PSBT_IN_TAP_BIP32_DERIVATION key that is too long (incorrectly serialized as compressed DER)",+      "hex": "70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757221602fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000000000",+      "base64": "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXIhYC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIZAHcrLadWAACAAQAAgAAAAIABAAAAAAAAAAAAAA=="+    },+    {+      "case": "PSBT With PSBT_OUT_TAP_INTERNAL_KEY key that is too long (incorrectly serialized as compressed DER)",+      "hex": "70736274ff01007d020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02887b0100000000001600142382871c7e8421a00093f754d91281e675874b9f606b042a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000001052102fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa23200",+      "base64": "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAABBSEC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIA"+    },+    {+      "case": "PSBT With PSBT_OUT_TAP_BIP32_DERIVATION key that is too long (incorrectly serialized as compressed DER)",+      "hex": "70736274ff01007d020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02887b0100000000001600142382871c7e8421a00093f754d91281e675874b9f606b042a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07570000220702fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da7560000800100008000000080010000000000000000",+      "base64": "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAiBwL+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAAA=="+    },+    {+      "case": "PSBT With PSBT_IN_TAP_SCRIPT_SIG key that is too long (incorrectly serialized as compressed DER)",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6924214022cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094089756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb0000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJCFAIssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20s2XDhX1P8DIL5UP1WD/qRm3YXK+AXNoqJkTrwdPQAsJQIl1aqNznMxonsD886NgvjLMC1mxbpOh6LtGBXJrLKej/3BsQXZkljKyzGjh+RK4pXjjcZzncQiFx6lm9JvNQ8sAAA=="+    },+    {+      "case": "PSBT With PSBT_IN_TAP_SCRIPT_SIG signature that is too long",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094289756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb01010000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlCiXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywEBAAA="+    },+    {+      "case": "PSBT With PSBT_IN_TAP_SCRIPT_SIG signature that is too short",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b093f89756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd430000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwk/iXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DAAA="+    },+    {+      "case": "PSBT With PSBT_IN_TAP_LEAF_SCRIPT Control block that is too long",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926315c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f80023202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc00000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJjFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgAIyAssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20qzAAAA="+    },+    {+      "case": "PSBT With PSBT_IN_TAP_LEAF_SCRIPT Control block that is too short",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926115c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e123202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc00000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJhFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4SMgLLE6xoJI3oBqpqNlnPPAPraCHQnIEUpOho/r3oZbttKswAAA"+    }+  ],+  "valid": [+    {+      "case": "PSBT with one P2TR key only input with internal key and its derivation path",+      "hex": "70736274ff010052020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a01000000160014768e1eeb4cf420866033f80aceff0f9720744969000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232002202036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d818772b2da7540000800100008000000080000000000000000000",+      "base64": "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgAiAgNrdyptt02HU8mKgnlY3mx4qzMSEJ830+AwRIQkLs5z2Bh3Ky2nVAAAgAEAAIAAAACAAAAAAAAAAAAA"+    },+    {+      "case": "PSBT with one P2TR key only input with internal key, its derivation path, and signature",+      "hex": "70736274ff010052020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a01000000160014768e1eeb4cf420866033f80aceff0f9720744969000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757011340bb53ec917bad9d906af1ba87181c48b86ace5aae2b53605a725ca74625631476fc6f5baedaf4f2ee0f477f36f58f3970d5b8273b7e497b97af2e3f125c97af342116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232002202036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d818772b2da7540000800100008000000080000000000000000000",+      "base64": "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1cBE0C7U+yRe62dkGrxuocYHEi4as5aritTYFpyXKdGJWMUdvxvW67a9PLuD0d/NvWPOXDVuCc7fkl7l68uPxJcl680IRb+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAARcg/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIAIgIDa3cqbbdNh1PJioJ5WN5seKszEhCfN9PgMESEJC7Oc9gYdystp1QAAIABAACAAAAAgAAAAAAAAAAAAA=="+    },+    {+      "case": "PSBT with one P2TR key only output with internal key and its derivation path",+      "hex": "70736274ff01005e020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000",+      "base64": "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSARJNp67JLM0GyVRWJkf0N7E4uVchqEvivyJ2u92rPmcSEHESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEZAHcrLadWAACAAQAAgAAAAIAAAAAABQAAAAA="+    },+    {+      "case": "PSBT with one P2TR script path only input with dummy internal key, scripts, derivation paths for keys in the scripts, and merkle root",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f823202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc04215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac097c6e6fea5ff714ff5724499990810e406e98aa10f5bf7e5f6784bc1d0a9a6ce23204320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2acc06215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f82320fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9acc021162cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d23901cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09772b2da7560000800100008002000080000000000000000021164320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b23901115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8772b2da75600008001000080010000800000000000000000211650929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2116fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca939016f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970772b2da7560000800100008003000080000000000000000001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0011820f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA"+    },+    {+      "case": "PSBT with one P2TR script path only output with dummy internal key, taproot tree, and script key derivation paths",+      "hex": "70736274ff01005e020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a010000002251200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2320001052050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac001066f02c02220736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02ac02c02220631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969ac01c0222044faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c4273ac210744faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c42733901f06b798b92a10ed9a9d0bbfd3af173a53b1617da3a4159ca008216cd856b2e0e772b2da75600008001000080010000800000000003000000210750929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2107631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969390118ace409889785e0ea70ceebb8e1ca892a7a78eaede0f2e296cf435961a8f4ca772b2da756000080010000800200008000000000030000002107736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02390129a5b4915090162d759afd3fe0f93fa3326056d0b4088cb933cae7826cb8d82c772b2da7560000800100008003000080000000000300000000",+      "base64": "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGbwLAIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcAiIET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA"+    },+    {+      "case": "PSBT with one P2TR script path only input with dummy internal key, scripts, script key derivation paths, merkle root, and script path signatures",+      "hex": "70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b0940bf818d9757d6ffeb538ba057fb4c1fc4e0f5ef186e765beb564791e02af5fd3d5e2551d4e34e33d86f276b82c99c79aed3f0395a081efcd2cc2c65dd7e693d7941144320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f840e1f1ab6fabfa26b236f21833719dc1d428ab768d80f91f9988d8abef47bfb863bb1f2a529f768c15f00ce34ec283cdc07e88f8428be28f6ef64043c32911811a4114fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca96f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae97040ec1f0379206461c83342285423326708ab031f0da4a253ee45aafa5b8c92034d8b605490f8cd13e00f989989b97e215faa36f12dee3693d2daccf3781c1757f66215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f823202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc04215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac097c6e6fea5ff714ff5724499990810e406e98aa10f5bf7e5f6784bc1d0a9a6ce23204320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2acc06215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f82320fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9acc021162cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d23901cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09772b2da7560000800100008002000080000000000000000021164320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b23901115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8772b2da75600008001000080010000800000000000000000211650929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2116fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca939016f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970772b2da7560000800100008003000080000000000000000001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0011820f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000",+      "base64": "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlAv4GNl1fW/+tTi6BX+0wfxOD17xhudlvrVkeR4Cr1/T1eJVHU404z2G8na4LJnHmu0/A5Wgge/NLMLGXdfmk9eUEUQyCwvxbwEbU+p75hWSSqfyfl0prSDqEVXYSGdsO60bIRXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+EDh8atvq/omsjbyGDNxncHUKKt2jYD5H5mI2KvvR7+4Y7sfKlKfdowV8AzjTsKDzcB+iPhCi+KPbvZAQ8MpEYEaQRT6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqW99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwQOwfA3kgZGHIM0IoVCMyZwirAx8NpKJT7kWq+luMkgNNi2BUkPjNE+APmJmJuX4hX6o28S3uNpPS2szzeBwXV/ZiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA"+    }+  ]+}
+ data/psbt_core32.json view
@@ -0,0 +1,8 @@+{+ "description": "Files Bitcoin Core 32.1 wrote on a regtest chain for a payment of 1 BTC from a coin of 50 BTC: the PSBT walletcreatefundedpsbt gives in version 0 and in version 2, the version 2 one signed by walletprocesspsbt without finalizing, then finalized, and the transaction finalizepsbt extracts. See the README of the haskoin-wallet vectors in test/data/psbt.",+ "prepared_v0": "cHNidP8BAHcCAAAAAZzolVrP9CarlPjUSHujJgVmsHiFB+Mm+Bafv63Mct7+AAAAAAD9////AgDh9QUAAAAAGXapFKvIhl67bbYllWAxY5ddm6o2RjLKiKw2CBAkAQAAABl2qRRFWqUMJmMUF3HrrCCW43gzkRIn1oisAAAAAAABAIcCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wMBZgD/////AgDyBSoBAAAAGXapFMgNVSI+LxBhNjrdKnsDY8KFvymNiKwAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QAAAAAiBgMvZDcCIlbIVOAJDn1si2XpZgbvf0BTmeDwGHyTCXrA+Ri8Vg+VLAAAgAAAAIAAAACAAAAAAAAAAAAAACICA3by5wZFjddWc7oZuO/YQu/d2bwK5B4xM53/z5F7bbbrGLxWD5UsAACAAAAAgAAAAIABAAAAAAAAAAA=",+ "prepared_v2": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAIcCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wMBZgD/////AgDyBSoBAAAAGXapFMgNVSI+LxBhNjrdKnsDY8KFvymNiKwAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QAAAAAiBgMvZDcCIlbIVOAJDn1si2XpZgbvf0BTmeDwGHyTCXrA+Ri8Vg+VLAAAgAAAAIAAAACAAAAAAAAAAAABDiCc6JVaz/Qmq5T41Eh7oyYFZrB4hQfjJvgWn7+tzHLe/gEPBAAAAAABEAT9////AAEDCADh9QUAAAAAAQQZdqkUq8iGXrtttiWVYDFjl12bqjZGMsqIrAAiAgN28ucGRY3XVnO6Gbjv2ELv3dm8CuQeMTOd/8+Re2226xi8Vg+VLAAAgAAAAIAAAACAAQAAAAAAAAABAwg2CBAkAQAAAAEEGXapFEVapQwmYxQXceusIJbjeDOREifWiKwA",+ "signed_v2": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAIcCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wMBZgD/////AgDyBSoBAAAAGXapFMgNVSI+LxBhNjrdKnsDY8KFvymNiKwAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QAAAAAiAgMvZDcCIlbIVOAJDn1si2XpZgbvf0BTmeDwGHyTCXrA+UcwRAIgLeMOTixwPqb3Q/BHIdvx2UaMZSeREBHXQFd48xD3urQCIBoZ1qD0T7FwE+JTWqLJ4hni3HJwM6PvpMB+dAKL1PaeASIGAy9kNwIiVshU4AkOfWyLZelmBu9/QFOZ4PAYfJMJesD5GLxWD5UsAACAAAAAgAAAAIAAAAAAAAAAAAEOIJzolVrP9CarlPjUSHujJgVmsHiFB+Mm+Bafv63Mct7+AQ8EAAAAAAEQBP3///8AAQMIAOH1BQAAAAABBBl2qRSryIZeu222JZVgMWOXXZuqNkYyyoisACICA3by5wZFjddWc7oZuO/YQu/d2bwK5B4xM53/z5F7bbbrGLxWD5UsAACAAAAAgAAAAIABAAAAAAAAAAEDCDYIECQBAAAAAQQZdqkURVqlDCZjFBdx66wgluN4M5ESJ9aIrAA=",+ "finalized_v2": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAIcCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wMBZgD/////AgDyBSoBAAAAGXapFMgNVSI+LxBhNjrdKnsDY8KFvymNiKwAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QAAAAABB2pHMEQCIC3jDk4scD6m90PwRyHb8dlGjGUnkRAR10BXePMQ97q0AiAaGdag9E+xcBPiU1qiyeIZ4txycDOj76TAfnQCi9T2ngEhAy9kNwIiVshU4AkOfWyLZelmBu9/QFOZ4PAYfJMJesD5AQ4gnOiVWs/0JquU+NRIe6MmBWaweIUH4yb4Fp+/rcxy3v4BDwQAAAAAARAE/f///wABAwgA4fUFAAAAAAEEGXapFKvIhl67bbYllWAxY5ddm6o2RjLKiKwAIgIDdvLnBkWN11Zzuhm479hC793ZvArkHjEznf/PkXtttusYvFYPlSwAAIAAAACAAAAAgAEAAAAAAAAAAQMINggQJAEAAAABBBl2qRRFWqUMJmMUF3HrrCCW43gzkRIn1oisAA==",+ "final_tx": "02000000019ce8955acff426ab94f8d4487ba3260566b0788507e326f8169fbfadcc72defe000000006a47304402202de30e4e2c703ea6f743f04721dbf1d9468c6527911011d7405778f310f7bab402201a19d6a0f44fb17013e2535aa2c9e219e2dc727033a3efa4c07e74028bd4f69e0121032f6437022256c854e0090e7d6c8b65e96606ef7f405399e0f0187c93097ac0f9fdffffff0200e1f505000000001976a914abc8865ebb6db62595603163975d9baa364632ca88ac36081024010000001976a914455aa50c2663141771ebac2096e37833911227d688ac00000000"+}
haskoin-core.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           haskoin-core-version:        1.4.0+version:        1.5.0 synopsis:       Bitcoin & Bitcoin Cash library for Haskell description:    Please see the README on GitHub at <https://github.com/jprupp/haskoin-core#readme> category:       Bitcoin, Finance, Network@@ -17,12 +17,17 @@ license-file:   LICENSE build-type:     Simple extra-source-files:+    data/block_100000.hex     data/bip341.json     data/complex_psbt.json     data/forkid_script_tests.json     data/forkid_sighash.json     data/key_io_invalid.json     data/key_io_valid.json+    data/psbt_bip174.json+    data/psbt_bip370.json+    data/psbt_bip371.json+    data/psbt_core32.json     data/rfc6979cash.json     data/rfc6979core.json     data/rfc6979DERcash.json@@ -69,8 +74,10 @@       Haskoin.Crypto.Keys.Mnemonic       Haskoin.Crypto.Signature       Haskoin.Network+      Haskoin.Network.BlockFilter       Haskoin.Network.Bloom       Haskoin.Network.Common+      Haskoin.Network.CompactBlock       Haskoin.Network.Constants       Haskoin.Network.Data       Haskoin.Network.Message@@ -83,7 +90,7 @@       Haskoin.Transaction.Builder.Sign       Haskoin.Transaction.Common       Haskoin.Transaction.Genesis-      Haskoin.Transaction.Partial+      Haskoin.Transaction.Psbt       Haskoin.Transaction.Segwit       Haskoin.Transaction.Taproot       Haskoin.Util@@ -109,6 +116,7 @@     , array >=0.5.4.0     , base >=4.9 && <5     , base16 >=2.0+    , base64 >=1.0     , binary >=0.8.8     , bytes >=0.17     , bytestring >=0.10.10.0@@ -127,12 +135,13 @@     , ram >=0.22.0     , safe >=0.3.18     , scientific >=0.3.6.2-    , secp256k1-haskell >=1.7.0+    , secp256k1-haskell >=2.0.0     , split >=0.2.3.3     , string-conversions >=0.4.0.1     , text >=1.2.3.0     , time >=1.9.3     , transformers >=0.5.6.2+    , unicode-transforms >=0.4     , unordered-containers >=0.2.10.0     , vector >=0.12.1.2   default-language: Haskell2010@@ -152,7 +161,7 @@       Haskoin.Crypto.SignatureSpec       Haskoin.NetworkSpec       Haskoin.ScriptSpec-      Haskoin.Transaction.PartialSpec+      Haskoin.Transaction.PsbtSpec       Haskoin.Transaction.TaprootSpec       Haskoin.TransactionSpec       Haskoin.UtilSpec@@ -188,12 +197,13 @@     , ram >=0.22.0     , safe >=0.3.18     , scientific >=0.3.6.2-    , secp256k1-haskell >=1.7.0+    , secp256k1-haskell >=2.0.0     , split >=0.2.3.3     , string-conversions >=0.4.0.1     , text >=1.2.3.0     , time >=1.9.3     , transformers >=0.5.6.2+    , unicode-transforms >=0.4     , unordered-containers >=0.2.10.0     , vector >=0.12.1.2   default-language: Haskell2010
src/Haskoin/Address.hs view
@@ -8,6 +8,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE NoFieldSelectors #-}  -- |@@ -18,15 +19,15 @@ -- Stability   : experimental -- Portability : POSIX ----- Base58, CashAddr, Bech32 address and WIF private key serialization support.+-- Base58, CashAddr and Bech32 address serialization support. module Haskoin.Address   ( -- * Addresses     Address       ( PubKeyAddress,         ScriptAddress,-        WitnessAddress,         WitnessPubKeyAddress,         WitnessScriptAddress,+        WitnessAddress,         Script32Address,         CashAddress       ),@@ -49,6 +50,9 @@     addressVersion,     addressBytes,     addrToText,+    addrToBase58,+    addrToBech32,+    addrToCash,     textToAddr,     bech32ToAddr,     cashToAddr,@@ -77,13 +81,11 @@ where  import Control.Applicative (Alternative ((<|>)))-import Control.Arrow (second) import Control.DeepSeq (NFData)-import Control.Monad (guard, (<=<))+import Control.Monad (guard, when, (<=<)) import Crypto.Secp256k1-import Data.Aeson (ToJSON (toJSON), Value, withText)-import Data.Aeson.Encoding (Encoding, null_, text)-import Data.Aeson.Types (Encoding, Parser, ToJSON (toJSON), Value, withText)+import Data.Aeson (ToJSON (toJSON), withText)+import Data.Aeson.Encoding (text) import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import Data.ByteString qualified as B@@ -91,7 +93,7 @@ import Data.Bytes.Put (MonadPut (putByteString, putWord64be, putWord8), runPutS) import Data.Bytes.Serial (Serial (..)) import Data.Hashable (Hashable)-import Data.Maybe (isNothing)+import Data.Maybe (fromMaybe, isJust) import Data.Serialize (Serialize (..)) import Data.Text (Text) import Data.Text qualified as T@@ -108,6 +110,12 @@ import Haskoin.Util  -- | Address format for Bitcoin and Bitcoin Cash.+--+-- The 'WitnessAddress' and 'CashAddress' patterns can only be used to match.+-- Build those addresses with 'witnessAddress' and 'cashAddress', which check+-- the version and the payload length, and which return the dedicated+-- constructor when one exists. This keeps every address with a single+-- representation, so two equal addresses always compare equal. data Address   = -- | pay to public key hash (regular)     PubKeyAddress@@ -129,53 +137,100 @@       { -- | HASH256 hash of script         hash256 :: !Hash256       }-  | -- | other witness address-    WitnessAddress+  | -- | SegWit address with a witness version other than zero, such as+    -- Taproot. The version is between 1 and 16 and the program is between 2+    -- and 40 bytes long.+    OtherWitnessAddress       { version :: !Word8,         bytes :: !ByteString       }   | -- | pay to script hash (256-bit)     Script32Address-      { -- | HAS256 hash of script+      { -- | HASH256 hash of script         hash256 :: !Hash256       }-  | -- | other CashAddr-    CashAddress+  | -- | CashAddr with a type or hash size that has no dedicated constructor.+    -- Token-aware addresses have type 2 (P2PKH) or 3 (P2SH). The type is+    -- below 16 and the hash has one of the sizes the standard allows.+    OtherCashAddress       { version :: !Word8,         bytes :: !ByteString       }   deriving     (Eq, Ord, Generic, Show, Read, Hashable, NFData) +-- | Match a SegWit address with a witness version other than zero. Use+-- 'witnessAddress' to build one.+pattern WitnessAddress :: Word8 -> ByteString -> Address+pattern WitnessAddress v d <- OtherWitnessAddress v d++-- | Match a CashAddr that has no dedicated constructor, such as a token-aware+-- address. Use 'cashAddress' to build one.+pattern CashAddress :: Word8 -> ByteString -> Address+pattern CashAddress v d <- OtherCashAddress v d++{-# COMPLETE+  PubKeyAddress,+  ScriptAddress,+  WitnessPubKeyAddress,+  WitnessScriptAddress,+  WitnessAddress,+  Script32Address,+  CashAddress+  #-}++-- | Highest witness version defined by BIP-141.+maxWitnessVersion :: Word8+maxWitnessVersion = 16++-- | Witness program length limits from BIP-141.+minWitnessProgramLength, maxWitnessProgramLength :: Int+minWitnessProgramLength = 2+maxWitnessProgramLength = 40++-- | Highest CashAddr type. The type byte holds the type in four bits.+maxCashAddrType :: Word8+maxCashAddrType = 15++-- | Hash sizes that the CashAddr version byte can describe.+cashAddrHashLengths :: [Int]+cashAddrHashLengths = [20, 24, 28, 32, 40, 48, 56, 64]++-- | CashAddr types for token-aware addresses, introduced with CashTokens.+-- They lock coins with the same script as the plain P2PKH and P2SH types.+-- The type only tells the sender that the receiver can handle tokens.+tokenPubKeyType, tokenScriptType :: Word8+tokenPubKeyType = 2+tokenScriptType = 3+ -- | Binary serialization for 'Address' type is not standard. -- Do not expect other other software to understand these. -- Use text format or output scripts for exchange. instance Serial Address where-  serialize (PubKeyAddress k) = do-    putWord8 0x00-    serialize k-  serialize (ScriptAddress s) = do-    putWord8 0x01-    serialize s-  serialize (WitnessPubKeyAddress h) = do-    putWord8 0x02-    serialize h-  serialize (WitnessScriptAddress s) = do-    putWord8 0x03-    serialize s-  serialize (WitnessAddress v d) = do-    putWord8 0x04-    putWord8 v-    putWord64be (fromIntegral (B.length d))-    putByteString d-  serialize (Script32Address s) = do-    putWord8 0x05-    serialize s-  serialize (CashAddress v d) = do-    putWord8 0x06-    putWord8 v-    putWord64be (fromIntegral (B.length d))-    putByteString d+  serialize = \case+    PubKeyAddress k -> do+      putWord8 0x00+      serialize k+    ScriptAddress s -> do+      putWord8 0x01+      serialize s+    WitnessPubKeyAddress h -> do+      putWord8 0x02+      serialize h+    WitnessScriptAddress s -> do+      putWord8 0x03+      serialize s+    OtherWitnessAddress v d -> do+      putWord8 0x04+      putWord8 v+      putSized d+    Script32Address s -> do+      putWord8 0x05+      serialize s+    OtherCashAddress v d -> do+      putWord8 0x06+      putWord8 v+      putSized d    deserialize =     getWord8 >>= \case@@ -183,20 +238,38 @@       0x01 -> ScriptAddress <$> deserialize       0x02 -> WitnessPubKeyAddress <$> deserialize       0x03 -> WitnessScriptAddress <$> deserialize-      0x04 ->-        WitnessAddress-          <$> getWord8-          <*> (getByteString . fromIntegral =<< getWord64be)+      0x04 -> do+        v <- getWord8+        d <- getSized maxWitnessProgramLength+        checked "SegWit" (witnessAddress v d)       0x05 -> Script32Address <$> deserialize       0x06 -> do-        CashAddress-          <$> getWord8-          <*> (getByteString . fromIntegral =<< getWord64be)+        v <- getWord8+        d <- getSized (maximum cashAddrHashLengths)+        checked "CashAddr" (cashAddress v d)       b ->         fail . T.unpack $           "Could not decode address type byte: "             <> encodeHex (B.singleton b)+    where+      checked kind = maybe (fail ("Invalid " <> kind <> " address")) return +-- | Write bytes behind their length. The length is a 64-bit word to stay+-- compatible with data serialized by earlier versions.+putSized :: (MonadPut m) => ByteString -> m ()+putSized d = do+  putWord64be (fromIntegral (B.length d))+  putByteString d++-- | Read bytes written by 'putSized'. The length is checked against a maximum+-- while it is still a 64-bit word, so a corrupt or hostile length can neither+-- overflow an 'Int' nor ask for a huge allocation.+getSized :: (MonadGet m) => Int -> m ByteString+getSized maxLen = do+  n <- getWord64be+  when (n > fromIntegral maxLen) $ fail "Address payload too long"+  getByteString (fromIntegral n)+ instance Serialize Address where   put = serialize   get = deserialize@@ -228,7 +301,7 @@  -- | 'Address' is another type of SegWit address, not covered above. isWitnessAddress :: Address -> Bool-isWitnessAddress WitnessAddress {} = True+isWitnessAddress OtherWitnessAddress {} = True isWitnessAddress _ = False  -- | 'Address' pays to a 256-bit script hash.@@ -238,7 +311,7 @@  -- | 'Address' is another type of CashAddr, not covered above. isCashAddress :: Address -> Bool-isCashAddress CashAddress {} = True+isCashAddress OtherCashAddress {} = True isCashAddress _ = False  -- | Smart constructor for P2PKH address.@@ -257,150 +330,179 @@ witnessScriptAddress :: Hash256 -> Address witnessScriptAddress = WitnessScriptAddress --- | Smart constructor for other SegWit address.+-- | Smart constructor for a SegWit address from its witness version and+-- program. Version zero programs must be 20 or 32 bytes long and become+-- 'WitnessPubKeyAddress' or 'WitnessScriptAddress'. Other versions keep the+-- program as it is, as long as it is between 2 and 40 bytes long. witnessAddress :: Word8 -> ByteString -> Maybe Address witnessAddress v bs = do-  guard $ v <= 16-  guard $ B.length bs >= 2 && B.length bs <= 40-  case v of-    0 -> case B.length bs of-      20 -> do-        h <- eitherToMaybe $ runGetS deserialize bs-        return $ WitnessPubKeyAddress h-      32 -> do-        h <- eitherToMaybe $ runGetS deserialize bs-        return $ WitnessScriptAddress h+  guard $ v <= maxWitnessVersion+  guard $ B.length bs >= minWitnessProgramLength+  guard $ B.length bs <= maxWitnessProgramLength+  if v == 0+    then case B.length bs of+      20 -> WitnessPubKeyAddress <$> decodeHash bs+      32 -> WitnessScriptAddress <$> decodeHash bs       _ -> Nothing-    _ -> return $ WitnessAddress v bs+    else return $ OtherWitnessAddress v bs  -- | Smart constructor for P2SH32 address. script32Address :: Hash256 -> Address script32Address = Script32Address --- | Smart constructor for CashAddr.+-- | Smart constructor for a CashAddr from its type and hash. P2PKH and P2SH+-- types with a 20-byte hash become 'PubKeyAddress' and 'ScriptAddress', and+-- the P2SH type with a 32-byte hash becomes 'Script32Address'. Every other+-- combination that the standard allows is kept as it is. cashAddress :: Word8 -> ByteString -> Maybe Address cashAddress v b = do-  guard $ v < 16-  guard $ B.length b `elem` [20, 24, 28, 32, 40, 48, 56, 64]-  case v of-    0 -> case B.length b of-      20 -> do-        h <- eitherToMaybe $ runGetS deserialize b-        return $ pubKeyAddress h-      _ -> return $ CashAddress v b-    1 -> case B.length b of-      20 -> do-        h <- eitherToMaybe $ runGetS deserialize b-        return $ scriptAddress h-      32 -> do-        h <- eitherToMaybe $ runGetS deserialize b-        return $ script32Address h-      _ -> return $ CashAddress v b-    _ -> return $ CashAddress v b+  guard $ v <= maxCashAddrType+  guard $ B.length b `elem` cashAddrHashLengths+  case (v, B.length b) of+    (0, 20) -> PubKeyAddress <$> decodeHash b+    (1, 20) -> ScriptAddress <$> decodeHash b+    (1, 32) -> Script32Address <$> decodeHash b+    _ -> return $ OtherCashAddress v b +-- | Read a hash from bytes of the right length.+decodeHash :: (Serial a) => ByteString -> Maybe a+decodeHash = eitherToMaybe . runGetS deserialize++-- | The 160-bit hash an address pays to, if it pays to one. Token-aware+-- CashAddr addresses pay to the same hash as their plain counterparts. addressHash160 :: Address -> Maybe Hash160-addressHash160 (PubKeyAddress h) = Just h-addressHash160 (ScriptAddress h) = Just h-addressHash160 (WitnessPubKeyAddress h) = Just h-addressHash160 _ = Nothing+addressHash160 = \case+  PubKeyAddress h -> Just h+  ScriptAddress h -> Just h+  WitnessPubKeyAddress h -> Just h+  OtherCashAddress v b+    | (v == tokenPubKeyType || v == tokenScriptType) && B.length b == 20 ->+        decodeHash b+  _ -> Nothing +-- | The 256-bit hash an address pays to, if it pays to one. Token-aware+-- CashAddr addresses pay to the same hash as their plain counterparts. addressHash256 :: Address -> Maybe Hash256-addressHash256 (WitnessScriptAddress h) = Just h-addressHash256 (Script32Address h) = Just h-addressHash256 _ = Nothing+addressHash256 = \case+  WitnessScriptAddress h -> Just h+  Script32Address h -> Just h+  OtherCashAddress v b+    | v == tokenScriptType && B.length b == 32 ->+        decodeHash b+  _ -> Nothing +-- | The witness version of a SegWit address, or the CashAddr type of any+-- other address. addressVersion :: Address -> Word8-addressVersion (PubKeyAddress _) = 0-addressVersion (ScriptAddress _) = 1-addressVersion (WitnessPubKeyAddress _) = 0-addressVersion (WitnessScriptAddress _) = 0-addressVersion (WitnessAddress v _) = v-addressVersion (Script32Address _) = 1-addressVersion (CashAddress v _) = v+addressVersion = \case+  PubKeyAddress _ -> 0+  ScriptAddress _ -> 1+  WitnessPubKeyAddress _ -> 0+  WitnessScriptAddress _ -> 0+  OtherWitnessAddress v _ -> v+  Script32Address _ -> 1+  OtherCashAddress v _ -> v +-- | The hash or witness program of an address, without any prefix. addressBytes :: Address -> ByteString-addressBytes (PubKeyAddress h) = runPutS (serialize h)-addressBytes (ScriptAddress h) = runPutS (serialize h)-addressBytes (WitnessPubKeyAddress h) = runPutS (serialize h)-addressBytes (WitnessScriptAddress h) = runPutS (serialize h)-addressBytes (WitnessAddress _ b) = b-addressBytes (Script32Address h) = runPutS (serialize h)-addressBytes (CashAddress _ b) = runPutS (serialize b)+addressBytes = \case+  PubKeyAddress h -> hashBytes h+  ScriptAddress h -> hashBytes h+  WitnessPubKeyAddress h -> hashBytes h+  WitnessScriptAddress h -> hashBytes h+  OtherWitnessAddress _ b -> b+  Script32Address h -> hashBytes h+  OtherCashAddress _ b -> b +-- | Raw bytes of a hash.+hashBytes :: (Serial a) => a -> ByteString+hashBytes = runPutS . serialize++-- | JSON encoding uses the text form of the address. Encoding an address that+-- has no text form on the network, such as a SegWit address on Bitcoin Cash,+-- is a programming error and throws an 'error'. A loud failure is better than+-- a silent @null@ in the output. instance MarshalJSON Network Address where-  marshalValue net a = toJSON (addrToText net a)-  marshalEncoding net = maybe null_ text . addrToText net+  marshalValue net = toJSON . addrTextOrError net+  marshalEncoding net = text . addrTextOrError net   unmarshalValue net =     withText "address" $ \t ->       case textToAddr net t of         Nothing -> fail "could not decode address"         Just x -> return x --- | Convert address to human-readable string. Uses 'Base58', 'Bech32', or--- 'CashAddr' depending on network.+addrTextOrError :: Network -> Address -> Text+addrTextOrError net a = fromMaybe (error msg) (addrToText net a)+  where+    msg =+      "Address " <> show a <> " has no text form on network " <> net.name++-- | Convert address to human-readable string. Uses 'CashAddr' on Bitcoin Cash+-- networks, and 'Base58' or 'Bech32' on Bitcoin networks. addrToText :: Network -> Address -> Maybe Text-addrToText net a@PubKeyAddress {hash160 = h}-  | isNothing net.cashAddrPrefix =-      Just . encodeBase58Check . runPutS $ base58put net a-  | otherwise = cashAddrEncode net 0 (runPutS $ serialize h)-addrToText net a@ScriptAddress {hash160 = h}-  | isNothing net.cashAddrPrefix =-      Just . encodeBase58Check . runPutS $ base58put net a-  | otherwise =-      cashAddrEncode net 1 (runPutS $ serialize h)-addrToText net WitnessPubKeyAddress {hash160 = h} = do-  hrp <- net.bech32Prefix-  segwitEncode hrp 0 (B.unpack (runPutS $ serialize h))-addrToText net WitnessScriptAddress {hash256 = h} = do-  hrp <- net.bech32Prefix-  segwitEncode hrp 0 (B.unpack (runPutS $ serialize h))-addrToText net WitnessAddress {version = v, bytes = d} = do+addrToText net a+  | isJust net.cashAddrPrefix = addrToCash net a+  | otherwise = addrToBase58 net a <|> addrToBech32 net a++-- | Encode a P2PKH or P2SH address in the legacy 'Base58' format of the+-- network. Works on Bitcoin Cash networks too, for software that still needs+-- the legacy format there. Other address kinds have no 'Base58' form.+addrToBase58 :: Network -> Address -> Maybe Base58+addrToBase58 net = \case+  PubKeyAddress h -> Just $ base58 net.addrPrefix h+  ScriptAddress h -> Just $ base58 net.scriptPrefix h+  _ -> Nothing+  where+    base58 pfx h = encodeBase58Check . runPutS $ putWord8 pfx >> serialize h++-- | Encode a SegWit address in the 'Bech32' or 'Bech32m' format of the+-- network. Fails on networks without SegWit and for other address kinds.+addrToBech32 :: Network -> Address -> Maybe Bech32+addrToBech32 net a = do   hrp <- net.bech32Prefix-  segwitEncode hrp v (B.unpack d)-addrToText net Script32Address {hash256 = h}-  | isNothing net.cashAddrPrefix =-      Nothing-  | otherwise =-      cashAddrEncode net 1 (runPutS $ serialize h)-addrToText net CashAddress {version = v, bytes = h}-  | isNothing net.cashAddrPrefix =-      Nothing-  | otherwise =-      cashAddrEncode net v h+  case a of+    WitnessPubKeyAddress h -> segwitEncode hrp 0 (B.unpack (hashBytes h))+    WitnessScriptAddress h -> segwitEncode hrp 0 (B.unpack (hashBytes h))+    OtherWitnessAddress v d -> segwitEncode hrp v (B.unpack d)+    _ -> Nothing +-- | Encode an address in the 'CashAddr' format of the network. Fails on+-- networks without CashAddr and for SegWit addresses.+addrToCash :: Network -> Address -> Maybe CashAddr+addrToCash net = \case+  PubKeyAddress h -> cashAddrEncode net 0 (hashBytes h)+  ScriptAddress h -> cashAddrEncode net 1 (hashBytes h)+  Script32Address h -> cashAddrEncode net 1 (hashBytes h)+  OtherCashAddress v b -> cashAddrEncode net v b+  _ -> Nothing+ -- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network. textToAddr :: Network -> Text -> Maybe Address textToAddr net txt =   cashToAddr net txt <|> bech32ToAddr net txt <|> base58ToAddr net txt  cashToAddr :: Network -> Text -> Maybe Address-cashToAddr net txt = do-  (ver, bs) <- cashAddrDecode net txt-  case ver of-    0 -> case B.length bs of-      20 -> PubKeyAddress <$> eitherToMaybe (runGetS deserialize bs)-      _ -> return $ CashAddress ver bs-    1 -> case B.length bs of-      20 -> ScriptAddress <$> eitherToMaybe (runGetS deserialize bs)-      32 -> Script32Address <$> eitherToMaybe (runGetS deserialize bs)-      _ -> return $ CashAddress ver bs-    _ -> return $ CashAddress ver bs+cashToAddr net txt = uncurry cashAddress =<< cashAddrDecode net txt  bech32ToAddr :: Network -> Text -> Maybe Address bech32ToAddr net txt = do   hrp <- net.bech32Prefix-  (ver, bs) <- second B.pack <$> segwitDecode hrp txt-  case ver of-    0 -> case B.length bs of-      20 -> WitnessPubKeyAddress <$> eitherToMaybe (runGetS deserialize bs)-      32 -> WitnessScriptAddress <$> eitherToMaybe (runGetS deserialize bs)-      _ -> Nothing-    _ -> Just $ WitnessAddress ver bs+  (ver, prog) <- segwitDecode hrp txt+  witnessAddress ver (B.pack prog)  base58ToAddr :: Network -> Text -> Maybe Address-base58ToAddr net txt =-  eitherToMaybe . runGetS (base58get net) =<< decodeBase58Check txt+base58ToAddr net txt = do+  guard $ T.length txt <= maxBase58AddressLength+  bs <- decodeBase58Check txt+  eitherToMaybe $ runGetS (base58get net) bs +-- | A 'Base58' address holds 25 bytes: a version byte, a 20-byte hash and a+-- 4-byte checksum. That never takes more than 35 digits, because 58^35 is+-- larger than 256^25. Longer strings are rejected before any decoding, since+-- the time to decode grows with the square of the length.+maxBase58AddressLength :: Int+maxBase58AddressLength = 35+ base58get :: (MonadGet m) => Network -> m Address base58get net = do   pfx <- getWord8@@ -414,39 +516,35 @@       | x == net.scriptPrefix = return $ ScriptAddress a       | otherwise = fail "Does not recognize address prefix" -base58put :: (MonadPut m) => Network -> Address -> m ()-base58put net (PubKeyAddress h) = do-  putWord8 net.addrPrefix-  serialize h-base58put net (ScriptAddress h) = do-  putWord8 net.scriptPrefix-  serialize h-base58put _ _ = error "Cannot serialize this address as Base58"+-- | HASH160 of a public key in its serialized form.+pubKeyHash :: Ctx -> PublicKey -> Hash160+pubKeyHash ctx = addressHash . marshal ctx  -- | Obtain a standard pay-to-public-key-hash address from a public key. pubKeyAddr :: Ctx -> PublicKey -> Address-pubKeyAddr ctx = PubKeyAddress . addressHash . marshal ctx---- | Obtain a standard pay-to-public-key-hash (P2PKH) address from a 'Hash160'.-p2pkhAddr :: Hash160 -> Address-p2pkhAddr = PubKeyAddress+pubKeyAddr ctx = p2pkhAddr . pubKeyHash ctx  -- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a--- public key.-pubKeyWitnessAddr :: Ctx -> PublicKey -> Address-pubKeyWitnessAddr ctx =-  WitnessPubKeyAddress . addressHash . marshal ctx+-- public key. Returns 'Nothing' for an uncompressed key. BIP-143 makes witness+-- spends with uncompressed keys non-standard, so coins sent to such an address+-- could not be spent through the normal network.+pubKeyWitnessAddr :: Ctx -> PublicKey -> Maybe Address+pubKeyWitnessAddr ctx pk = do+  guard pk.compress+  return . p2wpkhAddr $ pubKeyHash ctx pk  -- | Obtain a backwards-compatible SegWit P2SH-P2WPKH address from a public key.-pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Address-pubKeyCompatWitnessAddr ctx =-  p2shAddr-    . addressHash-    . marshal ctx-    . PayWitnessPKHash-    . addressHash-    . marshal ctx+-- Returns 'Nothing' for an uncompressed key, for the same reason as+-- 'pubKeyWitnessAddr'.+pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Maybe Address+pubKeyCompatWitnessAddr ctx pk = do+  guard pk.compress+  return . payToScriptAddress ctx . PayWitnessPKHash $ pubKeyHash ctx pk +-- | Obtain a standard pay-to-public-key-hash (P2PKH) address from a 'Hash160'.+p2pkhAddr :: Hash160 -> Address+p2pkhAddr = PubKeyAddress+ -- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a -- 'Hash160'. p2wpkhAddr :: Hash160 -> Address@@ -472,10 +570,11 @@ -- | Compute a backwards-compatible SegWit P2SH-P2WSH address. payToNestedScriptAddress :: Ctx -> ScriptOutput -> Address payToNestedScriptAddress ctx =-  p2shAddr . addressHash . marshal ctx . toP2WSH . encodeOutput ctx+  p2shAddr . addressHash . marshal ctx . PayWitnessScriptHash . sha256 . marshal ctx --- | Encode an output script from an address. Will fail if using a--- pay-to-witness address on a non-SegWit network.+-- | Get the output script that locks coins sent to an address. Token-aware+-- CashAddr addresses use the same script as their plain counterparts. Other+-- CashAddr kinds have no known script, so the result is 'Nothing'. addressToOutput :: Address -> Maybe ScriptOutput addressToOutput =   \case@@ -483,13 +582,24 @@     ScriptAddress h -> Just (PayScriptHash h)     WitnessPubKeyAddress h -> Just (PayWitnessPKHash h)     WitnessScriptAddress h -> Just (PayWitnessScriptHash h)-    WitnessAddress v d -> Just (PayWitness v d)+    OtherWitnessAddress v d -> Just (PayWitness v d)     Script32Address h -> Just (PayScript32Hash h)-    CashAddress v a -> Nothing+    OtherCashAddress v b -> cashAddressOutput v b +-- | Output script for a CashAddr kind without a dedicated constructor.+cashAddressOutput :: Word8 -> ByteString -> Maybe ScriptOutput+cashAddressOutput v b+  | v == tokenPubKeyType && len == 20 = PayPKHash <$> decodeHash b+  | v == tokenScriptType && len == 20 = PayScriptHash <$> decodeHash b+  | v == tokenScriptType && len == 32 = PayScript32Hash <$> decodeHash b+  | otherwise = Nothing+  where+    len = B.length b+ -- | Get output script AST for an 'Address'. addressToScript :: Ctx -> Address -> Maybe Script-addressToScript ctx addr = encodeOutput ctx <$> addressToOutput addr+addressToScript ctx addr =+  addressToOutput addr >>= eitherToMaybe . encodeOutput ctx  -- | Encode address as output script in 'ByteString' form. addressToScriptBS :: Ctx -> Address -> Maybe ByteString@@ -509,16 +619,17 @@   where     e = "Could not decode address" --- | Get the 'Address' of a 'ScriptOutput'.+-- | Get the 'Address' of a 'ScriptOutput'. A pay-to-public-key output has no+-- address. Its coins are not spendable with the input script of the P2PKH+-- address of the same key, so reporting that address would mislead. outputAddress :: Ctx -> ScriptOutput -> Maybe Address-outputAddress ctx =+outputAddress _ =   \case     PayPKHash h -> Just $ PubKeyAddress h     PayScriptHash h -> Just $ ScriptAddress h-    PayPK k -> Just $ pubKeyAddr ctx k     PayWitnessPKHash h -> Just $ WitnessPubKeyAddress h     PayWitnessScriptHash h -> Just $ WitnessScriptAddress h-    PayWitness v d -> Just $ WitnessAddress v d+    PayWitness v d -> witnessAddress v d     PayScript32Hash h -> Just $ Script32Address h     _ -> Nothing @@ -528,4 +639,5 @@   \case     (RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr ctx key     (ScriptHashInput _ rdm) -> Just $ payToScriptAddress ctx rdm+    (NestedWitnessInput rdm) -> Just $ payToScriptAddress ctx rdm     _ -> Nothing
src/Haskoin/Address/Base58.hs view
@@ -26,19 +26,17 @@ import Data.Array (Array, assocs, listArray, (!), (//)) import Data.ByteString (ByteString) import Data.ByteString qualified as B-import Data.ByteString.Char8 qualified as C-import Data.Bytes.Get () import Data.Bytes.Put (runPutS) import Data.Bytes.Serial (Serial (serialize))-import Data.Char (chr, ord)-import Data.Maybe (fromMaybe, isJust, listToMaybe)+import Data.Char (chr) import Data.String.Conversions (cs) import Data.Text (Text) import Data.Text qualified as T+import Data.Text.Encoding qualified as E import Data.Word (Word8) import Haskoin.Crypto.Hash (checkSum32) import Haskoin.Util.Helpers (bsToInteger, integerToBS)-import Numeric (readInt, showIntAtBase)+import Numeric (showIntAtBase)  -- | 'Base58' classic Bitcoin address format. type Base58 = Text@@ -50,6 +48,9 @@ b58Array :: Array Int Word8 b58Array = listArray (0, 57) (B.unpack b58Data) +-- | Value of each byte as a Base58 digit. Bytes outside the alphabet map to+-- 'Nothing'. Every byte of a multi-byte UTF-8 character is outside the+-- alphabet, so no non-ASCII character can ever be read as a digit. b58InvArray :: Array Word8 (Maybe Int) b58InvArray =   listArray@@ -59,8 +60,7 @@   where     swap (i, c) = (c, Just i) --- | Convert a number less than or equal to provided integer into a 'Base58'--- character.+-- | Convert a number less than 58 into a 'Base58' character. b58 :: Int -> Word8 b58 = (b58Array !) @@ -73,17 +73,15 @@ encodeBase58I :: Integer -> Base58 encodeBase58I i = cs $ showIntAtBase 58 (chr . fromIntegral . b58) i "" --- | Decode a 'Base58' string into an arbitrary-length 'Integer'.-decodeBase58I :: Base58 -> Maybe Integer-decodeBase58I s =-  case go of-    Just (r, []) -> Just r-    _ -> Nothing+-- | Decode the UTF-8 bytes of a 'Base58' string into an 'Integer'. Fails on+-- the first byte that is not a 'Base58' digit.+decodeBase58I :: ByteString -> Maybe Integer+decodeBase58I = B.foldl' step (Just 0)   where-    p = isJust . b58' . fromIntegral . ord-    f = fromMaybe e . b58' . fromIntegral . ord-    go = listToMaybe $ readInt 58 p f (cs s)-    e = error "Could not decode base58"+    step acc w = do+      n <- acc+      d <- b58' w+      return (n * 58 + toInteger d)  -- | Encode an arbitrary 'ByteString' into a its 'Base58' representation, -- preserving leading zeroes.@@ -92,21 +90,29 @@   l <> r   where     (z, b) = B.span (== 0) bs-    l = cs $ B.replicate (B.length z) (b58 0) -- preserve leading 0's+    l = T.replicate (B.length z) "1" -- preserve leading 0's     r       | B.null b = T.empty       | otherwise = encodeBase58I $ bsToInteger b --- | Decode a 'Base58'-encoded 'Text' to a 'ByteString'.+-- | Decode a 'Base58'-encoded 'Text' to a 'ByteString'. Returns 'Nothing' if+-- the input contains any character outside the 'Base58' alphabet.+--+-- The time this takes grows with the square of the length of the input,+-- since the digits are accumulated into one big number. Bound the length+-- before decoding text from an untrusted source. No Bitcoin address is+-- longer than 35 characters and no extended key longer than 112;+-- 'Haskoin.Address.textToAddr', 'Haskoin.Crypto.Keys.fromWif' and+-- 'Haskoin.Crypto.Keys.xPrvImport' apply those bounds themselves. decodeBase58 :: Base58 -> Maybe ByteString decodeBase58 t =   B.append prefix <$> r   where-    (z, b) = B.span (== b58 0) (cs t)+    (z, b) = B.span (== b58 0) (E.encodeUtf8 t)     prefix = B.replicate (B.length z) 0 -- preserve leading 1's     r       | B.null b = Just B.empty-      | otherwise = integerToBS <$> decodeBase58I (cs b)+      | otherwise = integerToBS <$> decodeBase58I b  -- | Computes a checksum for the input 'ByteString' and encodes the input and -- the checksum as 'Base58'.@@ -115,11 +121,16 @@   (encodeBase58 . B.append bs . runPutS . serialize . checkSum32) bs  -- | Decode a 'Base58'-encoded string that contains a checksum. This function--- returns 'Nothing' if the input string contains invalid 'Base58' characters or--- if the checksum fails.+-- returns 'Nothing' if the input string contains invalid 'Base58' characters,+-- if it is too short to hold a checksum, or if the checksum fails. The cost+-- grows with the square of the length, as for 'decodeBase58': bound the+-- length of untrusted input first. decodeBase58Check :: Base58 -> Maybe ByteString-decodeBase58Check bs = do-  rs <- decodeBase58 bs-  let (res, chk) = B.splitAt (B.length rs - 4) rs+decodeBase58Check t = do+  rs <- decodeBase58 t+  guard $ B.length rs >= checkSumLength+  let (res, chk) = B.splitAt (B.length rs - checkSumLength) rs   guard $ chk == (runPutS . serialize . checkSum32) res   return res+  where+    checkSumLength = 4
src/Haskoin/Address/Bech32.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE NoFieldSelectors #-}  -- |--- Module      : Haskoin.Address.Base58+-- Module      : Haskoin.Address.Bech32 -- Copyright   : No rights reserved -- License     : MIT -- Maintainer  : jprupp@protonmail.ch@@ -51,7 +51,6 @@     (.|.),   ) import Data.ByteString qualified as B-import Data.Char (toUpper) import Data.Foldable (foldl') import Data.Functor.Identity (Identity, runIdentity) import Data.Ix (Ix (..))@@ -103,15 +102,21 @@ charset =   listArray (UnsafeWord5 0, UnsafeWord5 31) "qpzry9x8gf2tvdw0s3jn54khce6mua7l" --- | Convert a character to its five-bit value from 'Bech32' 'charset'.+-- | Inverse of 'charset'. Only the lowercase characters are present. The+-- decoder checks that the string is ASCII and not mixed case, then lowercases+-- it, before looking characters up here. No character from another alphabet+-- can therefore stand in for a 'Bech32' character.+charsetInverse :: Array Char (Maybe Word5)+charsetInverse =+  listArray ('0', 'z') (repeat Nothing) // map swap (assocs charset)+  where+    swap (w, c) = (c, Just w)++-- | Convert a lowercase character to its five-bit value from 'charset'. charsetMap :: Char -> Maybe Word5 charsetMap c-  | inRange (bounds inv) upperC = inv ! upperC+  | inRange (bounds charsetInverse) c = charsetInverse ! c   | otherwise = Nothing-  where-    upperC = toUpper c-    inv = listArray ('0', 'Z') (repeat Nothing) // map swap (assocs charset)-    swap (a, b) = (toUpper b, Just a)  -- | Calculate or validate 'Bech32' checksum. bech32Polymod :: [Word5] -> Word@@ -121,14 +126,16 @@       foldl' xor chk' [g | (g, i) <- zip generator [25 ..], testBit chk i]       where         generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]-        chk' = chk .<<. 5 `xor` fromWord5 value+        -- Keep only the low 25 bits before shifting, as the reference+        -- implementation does, so the accumulator never grows past 30 bits.+        chk' = ((chk .&. 0x1ffffff) .<<. 5) `xor` fromWord5 value  -- | Convert human-readable part of 'Bech32' string into a list of five-bit -- words. bech32HRPExpand :: HRP -> [Word5] bech32HRPExpand hrp =-  map (UnsafeWord5 . (.>>. 5)) hrpBytes-    ++ [UnsafeWord5 0]+  map (word5 . (.>>. 5)) hrpBytes+    ++ [word5 (0 :: Word8)]     ++ map word5 hrpBytes   where     hrpBytes = B.unpack $ E.encodeUtf8 hrp@@ -158,6 +165,10 @@ maxBech32Length :: Int maxBech32Length = 90 +-- | Number of five-bit words in a Bech32 checksum.+checksumLength :: Int+checksumLength = 6+ -- | Encode string of five-bit words into 'Bech32' using a provided -- human-readable part. Can fail if 'HRP' is invalid or result would be longer -- than 90 characters.@@ -170,25 +181,30 @@   guard $ T.length result <= maxBech32Length   return result +-- | BIP-173 allows only US-ASCII characters in the range 33 to 126 anywhere+-- in a 'Bech32' string.+isBech32Char :: Char -> Bool+isBech32Char c = c >= '\x21' && c <= '\x7e'+ -- | Check that human-readable part is valid for a 'Bech32' string. checkHRP :: HRP -> Bool-checkHRP hrp =-  not (T.null hrp)-    && T.all (\char -> char >= '\x21' && char <= '\x7e') hrp+checkHRP hrp = not (T.null hrp) && T.all isBech32Char hrp  -- | Decode human-readable 'Bech32' string into a human-readable part and a -- string of five-bit words. bech32Decode :: Bech32 -> Maybe (Bech32Encoding, HRP, [Word5]) bech32Decode bech32 = do   guard $ T.length bech32 <= maxBech32Length+  guard $ T.all isBech32Char bech32+  -- The string must be all uppercase or all lowercase, never a mix.   guard $ T.toUpper bech32 == bech32 || lowerBech32 == bech32   let (hrp, dat) = T.breakOnEnd "1" lowerBech32-  guard $ T.length dat >= 6+  guard $ T.length dat >= checksumLength   hrp' <- T.stripSuffix "1" hrp   guard $ checkHRP hrp'   dat' <- mapM charsetMap $ T.unpack dat   enc <- bech32VerifyChecksum hrp' dat'-  return (enc, hrp', take (T.length dat - 6) dat')+  return (enc, hrp', take (T.length dat - checksumLength) dat')   where     lowerBech32 = T.toLower bech32 @@ -218,13 +234,17 @@     go (value : dat') acc bits result =       go dat' acc' (bits' `rem` tobits) (result' : result)       where-        acc' = (acc .<<. frombits) .|. fromIntegral value+        -- Only the low (frombits + tobits - 1) bits of the accumulator are+        -- ever read, so masking to them keeps it from overflowing on long+        -- inputs.+        acc' = ((acc .<<. frombits) .|. fromIntegral value) .&. maxAcc         bits' = bits + frombits         result' =           [ (acc' .>>. b) .&. maxv           | b <- [bits' - tobits, bits' - 2 * tobits .. 0]           ]     maxv = (1 .<<. tobits) - 1+    maxAcc = (1 .<<. (frombits + tobits - 1)) - 1 {-# INLINE convertBits #-}  -- | Convert from eight-bit to five-bit word string, adding padding as required.@@ -232,7 +252,9 @@ toBase32 dat =   map word5 $ runIdentity $ convertBits (map fromIntegral dat) 8 5 yesPadding --- | Convert from five-bit word string to eight-bit word string, ignoring padding.+-- | Convert from five-bit word string to eight-bit word string. Fails if the+-- padding bits are not zero, or if there are enough of them to hold a whole+-- five-bit word, because the input is then not a canonical encoding. toBase256 :: [Word5] -> Maybe [Word8] toBase256 dat =   map fromIntegral <$> convertBits (map fromWord5 dat) 5 8 noPadding@@ -249,8 +271,10 @@ segwitDecode :: HRP -> Bech32 -> Maybe (Word8, Data) segwitDecode hrp addr = do   (enc, hrp', dat) <- bech32Decode addr-  guard $ (hrp == hrp') && not (null dat)-  let (UnsafeWord5 witver : datBase32) = dat+  guard $ hrp == hrp'+  (witver, datBase32) <- case dat of+    [] -> Nothing+    (v : rest) -> Just (fromWord5 v, rest)   decoded <- toBase256 datBase32   guard $ segwitCheck enc witver decoded   return (witver, decoded)@@ -260,6 +284,6 @@ segwitEncode :: HRP -> Word8 -> Data -> Maybe Text segwitEncode hrp witver witprog = do   guard $ segwitCheck enc witver witprog-  bech32Encode enc hrp $ UnsafeWord5 witver : toBase32 witprog+  bech32Encode enc hrp $ word5 witver : toBase32 witprog   where     enc = if witver == 0 then Bech32 else Bech32m
src/Haskoin/Address/CashAddr.hs view
@@ -44,8 +44,9 @@ import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.ByteString.Char8 qualified as C-import Data.Char (ord, toLower, toUpper)+import Data.Char (isAscii, ord) import Data.List (elemIndex, foldl')+import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding (decodeUtf8, encodeUtf8)@@ -58,7 +59,8 @@ -- from the wrong network. type CashPrefix = Text --- | 'CashAddr' version, until new address schemes appear it will be zero.+-- | 'CashAddr' type. Zero is P2PKH and one is P2SH. Two and three are their+-- token-aware forms, introduced with CashTokens. type CashVersion = Word8  -- | High level 'CashAddr' human-reabale string, with explicit or implicit prefix.@@ -72,15 +74,26 @@ charset :: String charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +-- | Number of five-bit characters in a 'Cash32' checksum.+checkSumLength :: Int+checkSumLength = 8+ -- | Get the 32-bit number associated with this 'Cash32' character. base32char :: Char -> Maybe Word8 base32char = fmap fromIntegral . (`elemIndex` charset) +-- | A 'CashAddr' string must be all lowercase or all uppercase. A mix of both+-- must be rejected. Only ASCII is allowed, so no character from another+-- alphabet can stand in for a letter.+singleCase :: Text -> Bool+singleCase t = T.all isAscii t && (T.toUpper t == t || T.toLower t == t)+ -- | High-Level: decode 'CashAddr' string if it is valid for the -- provided 'Network'. Prefix may be omitted from the string. cashAddrDecode :: Network -> CashAddr -> Maybe (CashVersion, ByteString) cashAddrDecode net ca = do   epfx <- net.cashAddrPrefix+  guard (singleCase ca)   let (cpfx, cdat) = T.breakOnEnd ":" (T.toLower ca)   guard (T.null cpfx || T.init cpfx == epfx)   (dpfx, ver, bs) <- cash32decodeType (epfx <> ":" <> cdat)@@ -95,19 +108,17 @@   cash32encodeType pfx cv bs  -- | Mid-Level: decode 'CashAddr' string containing arbitrary prefix, plus a--- version byte before the 'ByteString' that encodes type and length.+-- version byte before the 'ByteString' that encodes type and length. The+-- string must be the canonical encoding of its payload: padding bits must be+-- zero and there must be fewer than five of them. cash32decodeType :: Cash32 -> Maybe (CashPrefix, CashVersion, ByteString)-cash32decodeType ca' = do-  guard (T.toUpper ca' == ca' || ca == ca')-  (dpfx, bs) <- cash32decode ca-  guard (not (B.null bs))-  let vb = B.head bs-      pay = B.tail bs+cash32decodeType ca = do+  (dpfx, groups) <- cash32decodeGroups ca+  bs <- toBase256 groups+  (vb, pay) <- B.uncons bs   (ver, len) <- decodeVersionByte vb   guard (B.length pay == len)   return (dpfx, ver, pay)-  where-    ca = T.toLower ca'  -- | Mid-Level: encode 'CashAddr' string containing arbitrary prefix and -- 'CashVersion'. Length must be among those allowed by the standard.@@ -119,26 +130,33 @@   return (cash32encode pfx pl)  -- | Low-Level: decode 'Cash32' string. 'CashPrefix' must be part of the string.--- No version or hash length validation is performed.+-- No version or hash length validation is performed. The data may be any+-- sequence of five-bit groups, so bits left over after the last whole byte+-- are dropped. 'cash32decodeType' is the layer that enforces canonical+-- padding. cash32decode :: Cash32 -> Maybe (CashPrefix, ByteString) cash32decode text = do-  let bs = C.map toLower bs'-  guard (C.map toUpper bs' == bs' || bs == bs')-  let (pfx', dat) = C.breakEnd (== ':') bs+  (pfx, groups) <- cash32decodeGroups text+  return (pfx, wholeBytes groups)++-- | Check the checksum of a 'Cash32' string and return its prefix and its+-- data as five-bit groups, one per byte, without the checksum.+cash32decodeGroups :: Cash32 -> Maybe (CashPrefix, ByteString)+cash32decodeGroups text = do+  guard (singleCase text)+  let bs = encodeUtf8 (T.toLower text)+      (pfx', dat) = C.breakEnd (== ':') bs   pfx <-     if B.null pfx' || pfx' == C.singleton ':'       then Nothing       else Just (B.init pfx')   b32 <- B.pack <$> mapM base32char (C.unpack dat)+  guard (B.length b32 >= checkSumLength)   let px = B.map (.&. 0x1f) pfx       pd = px <> B.singleton 0 <> b32       cs = cash32Polymod pd-      bb = B.take (B.length b32 - 8) b32   guard (verifyCash32Polymod cs)-  let out = toBase256 bb-  return (decodeUtf8 pfx, out)-  where-    bs' = encodeUtf8 text+  return (decodeUtf8 pfx, B.take (B.length b32 - checkSumLength) b32)  -- | Low-Level: encode 'Cash32' string for 'CashPrefix' provided. Can encode -- arbitrary data. No prefix or length validation is performed.@@ -146,7 +164,7 @@ cash32encode pfx bs =   let b32 = toBase32 bs       px = B.map (.&. 0x1f) (encodeUtf8 pfx)-      pd = px <> B.singleton 0 <> b32 <> B.replicate 8 0+      pd = px <> B.singleton 0 <> b32 <> B.replicate checkSumLength 0       cs = cash32Polymod pd       c32 = B.map f (b32 <> cs)       f = fromIntegral . ord . (charset !!) . fromIntegral@@ -158,18 +176,26 @@ toBase32 =   B.pack     . map fromIntegral-    . fst+    -- With padding allowed the conversion always succeeds.+    . fromMaybe []     . convertBits True 8 5     . map fromIntegral     . B.unpack --- | Convert base of 'ByteString' from five to eight bits per byte. Ignore--- padding to be symmetric with respect to 'toBase32' function.-toBase256 :: ByteString -> ByteString+-- | Convert five-bit groups to the bytes they fill completely, dropping any+-- bits left over at the end.+wholeBytes :: ByteString -> ByteString+wholeBytes groups =+  B.take (5 * B.length groups `div` 8) . B.pack . map fromIntegral . fromMaybe [] $+    convertBits True 5 8 (map fromIntegral (B.unpack groups))++-- | Convert base of 'ByteString' from five to eight bits per byte. Fails if+-- the padding bits are not zero, or if there are enough of them to hold a+-- whole five-bit group, because the input is then not the canonical encoding+-- of its payload.+toBase256 :: ByteString -> Maybe ByteString toBase256 =-  B.pack-    . map fromIntegral-    . fst+  fmap (B.pack . map fromIntegral)     . convertBits False 5 8     . map fromIntegral     . B.unpack@@ -185,7 +211,7 @@     ls = [20, 24, 28, 32, 40, 48, 56, 64]  -- | Encode 'CashVersion' and length into version byte. Fail if version is--- larger than five bits, or length incorrect, since that is invalid.+-- larger than four bits, or length incorrect, since that is invalid. encodeVersionByte :: CashVersion -> Int -> Maybe Word8 encodeVersionByte ver len = do   guard (ver == ver .&. 0x0f)@@ -220,4 +246,4 @@  -- | Validate that polymod 'ByteString' (eight bytes) is equal to zero. verifyCash32Polymod :: ByteString -> Bool-verifyCash32Polymod = (== B.replicate 8 0)+verifyCash32Polymod = (== B.replicate checkSumLength 0)
src/Haskoin/Block/Common.hs view
@@ -41,7 +41,6 @@ import Data.Binary (Binary (..)) import Data.Bits import Data.ByteString qualified as B-import Data.ByteString.Builder (char7) import Data.ByteString.Lazy qualified as L import Data.Bytes.Get import Data.Bytes.Put@@ -76,13 +75,11 @@ instance Serial Block where   deserialize = do     header <- deserialize-    (VarInt c) <- deserialize-    txs <- replicateM (fromIntegral c) deserialize+    txs <- getVarIntList deserialize     return $ Block header txs   serialize (Block h txs) = do     serialize h-    putVarInt $ length txs-    forM_ txs serialize+    putVarIntList serialize txs  instance Serialize Block where   get = deserialize@@ -140,27 +137,44 @@  instance ToJSON BlockHash where   toJSON = String . blockHashToHex-  toEncoding = hexEncoding . L.reverse . runPutL . serialize+  toEncoding (BlockHash h) = reversedHexEncoding h  -- | Block hashes are reversed with respect to the in-memory byte order in a -- block hash when displayed. blockHashToHex :: BlockHash -> Text-blockHashToHex (BlockHash h) = encodeHex (B.reverse (runPutS (serialize h)))+blockHashToHex (BlockHash h) = reversedHex h  -- | Convert a human-readable hex block hash into a 'BlockHash'. Bytes are--- reversed as normal.+-- reversed as normal. The text must hold exactly 32 bytes of hex. hexToBlockHash :: Text -> Maybe BlockHash-hexToBlockHash hex = do-  bs <- B.reverse <$> decodeHex hex-  h <- eitherToMaybe (runGetS deserialize bs)-  return $ BlockHash h+hexToBlockHash = fmap BlockHash . hexToReversedHash +-- | Hex of a 256-bit hash in display order, which is the reverse of its byte+-- order in memory. Block hashes and Merkle roots are shown this way.+reversedHex :: Hash256 -> Text+reversedHex = encodeHex . B.reverse . runPutS . serialize++-- | JSON encoding of a 256-bit hash in display order.+reversedHexEncoding :: Hash256 -> Encoding+reversedHexEncoding = hexEncoding . L.reverse . runPutL . serialize++-- | Parse hex in display order into a 256-bit hash. Any input that is not+-- exactly 32 bytes of hex is rejected instead of being truncated.+hexToReversedHash :: Text -> Maybe Hash256+hexToReversedHash hex = do+  bs <- decodeHex hex+  guard (B.length bs == 32)+  eitherToMaybe $ runGetS deserialize $ B.reverse bs+ -- | Data type recording information of a 'Block'. The hash of a block is -- defined as the hash of this data structure, serialized. The block mining -- process involves finding a partial hash collision by varying the nonce in the -- 'BlockHeader' and/or additional entropy in the coinbase 'Transaction' of this -- 'Block'. Variations in the coinbase will result in different merkle roots in -- the 'BlockHeader'.+--+-- The JSON form shows the previous block hash and the Merkle root in display+-- order, like the @getblockheader@ RPC call of Bitcoin Core. data BlockHeader = BlockHeader   { version :: !Word32, --  4 bytes @@ -188,7 +202,7 @@     object       [ "version" .= v,         "prevblock" .= p,-        "merkleroot" .= encodeHex (runPutS $ serialize m),+        "merkleroot" .= reversedHex m,         "timestamp" .= t,         "bits" .= b,         "nonce" .= n@@ -198,7 +212,7 @@       mconcat         [ "version" `pair` word32 v,           "prevblock" `pair` toEncoding p,-          "merkleroot" `pair` hexEncoding (runPutL $ serialize m),+          "merkleroot" `pair` reversedHexEncoding m,           "timestamp" `pair` toEncoding t,           "bits" `pair` toEncoding b,           "nonce" `pair` toEncoding n@@ -210,12 +224,12 @@       BlockHeader         <$> o .: "version"         <*> o .: "prevblock"-        <*> (f =<< o .: "merkleroot")+        <*> (merkleRoot =<< o .: "merkleroot")         <*> o .: "timestamp"         <*> o .: "bits"         <*> o .: "nonce"     where-      f = maybe mzero return . (eitherToMaybe . runGetS deserialize <=< decodeHex)+      merkleRoot = maybe (fail "Invalid Merkle root") return . hexToReversedHash  instance Serial BlockHeader where   deserialize = do@@ -281,14 +295,11 @@   deserialize =     GetBlocks       <$> getWord32le-      <*> (repList =<< deserialize)+      <*> getVarIntList deserialize       <*> deserialize-    where-      repList (VarInt c) = replicateM (fromIntegral c) deserialize   serialize (GetBlocks v xs h) = do     putWord32le v-    putVarInt $ length xs-    forM_ xs serialize+    putVarIntList serialize xs     serialize h  instance Serialize GetBlocks where@@ -313,14 +324,11 @@   deserialize =     GetHeaders       <$> getWord32le-      <*> (repList =<< deserialize)+      <*> getVarIntList deserialize       <*> deserialize-    where-      repList (VarInt c) = replicateM (fromIntegral c) deserialize   serialize (GetHeaders v xs h) = do     putWord32le v-    putVarInt $ length xs-    forM_ xs serialize+    putVarIntList serialize xs     serialize h  instance Serialize GetHeaders where@@ -343,13 +351,9 @@   deriving (Eq, Show, Read, Generic, NFData)  instance Serial Headers where-  deserialize = Headers <$> (repList =<< deserialize)-    where-      repList (VarInt c) = replicateM (fromIntegral c) action-      action = liftM2 (,) deserialize deserialize-  serialize (Headers xs) = do-    putVarInt $ length xs-    forM_ xs $ \(a, b) -> serialize a >> serialize b+  deserialize = Headers <$> getVarIntList (liftM2 (,) deserialize deserialize)+  serialize (Headers xs) =+    putVarIntList (\(a, b) -> serialize a >> serialize b) xs  instance Serialize Headers where   put = serialize@@ -397,9 +401,13 @@            )  -- | Encode an 'Integer' to the compact number format used in the difficulty--- target of a block.+-- target of a block. The exponent is a single byte, so numbers that need more+-- than 255 bytes cannot be encoded and raise an error, as the assertion in+-- Bitcoin Core does. encodeCompact :: Integer -> Word32-encodeCompact i = nCompact+encodeCompact i+  | nSize > 255 = error "encodeCompact: number too large for the compact format"+  | otherwise = nCompact   where     i' = abs i     neg = i < 0
src/Haskoin/Block/Headers.hs view
@@ -45,40 +45,46 @@     initialChain,     genesisMap, -    -- ** Helper Functions-    appendBlocks,+    -- ** Validation     validBlock,     validCP,     afterLastCP,     bip34,     validVersion,     validTimeWarp,-    lastNoMinDiff,+    medianTime,+    mtp,++    -- ** Difficulty     nextWorkRequired,+    nextPowWorkRequired,+    calcNextWork,+    lastNoMinDiff,+    diffInterval,     nextEdaWorkRequired,     nextDaaWorkRequired,-    nextAsertWorkRequired,-    computeAsertBits,     computeTarget,     getSuitableBlock,-    nextPowWorkRequired,-    calcNextWork,+    nextAsertWorkRequired,+    asertAnchor,+    computeAsertBits,     isValidPOW,     blockPOW,     headerWork,-    diffInterval,++    -- ** Helper Functions     blockLocatorNodes,+    skipHeight,+    appendBlocks,     mineBlock,     computeSubsidy,-    mtp,     firstGreaterOrEqual,     lastSmallerOrEqual,   ) where -import Control.Applicative ((<|>)) import Control.DeepSeq-import Control.Monad (guard, mzero, unless, when)+import Control.Monad (mzero, unless, when) import Control.Monad.Except (ExceptT (..), runExceptT, throwError) import Control.Monad.State.Strict as State (StateT, get, gets, lift, modify) import Control.Monad.Trans.Maybe@@ -93,8 +99,10 @@ import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HashMap import Data.Hashable-import Data.List (sort, sortBy)-import Data.Maybe (fromMaybe, listToMaybe)+import Data.Int (Int32)+import Data.List (sort)+import Data.Maybe (fromMaybe, isJust)+import Data.Ord (comparing) import Data.Serialize (Serialize (..)) import Data.Typeable (Typeable) import Data.Word (Word32, Word64)@@ -102,8 +110,8 @@ import Haskoin.Block.Common import Haskoin.Crypto import Haskoin.Network.Data-import Haskoin.Transaction.Genesis import Haskoin.Util+import Numeric (showHex)  -- | Short version of the block hash. Uses the good end of the hash (the part -- that doesn't have a long string of zeroes).@@ -157,11 +165,14 @@   put = serialize   get = deserialize +-- | Two nodes are the same when they hold the same header. instance Eq BlockNode where   (==) = (==) `on` (.header) +-- | Nodes are ordered by height, then by header, so that equal nodes compare+-- as equal and nodes at the same height still have a total order. instance Ord BlockNode where-  compare = compare `on` (.height)+  compare = comparing (\bn -> (bn.height, bn.header))  -- | Memory-based header tree. data HeaderMemory = HeaderMemory@@ -281,8 +292,12 @@       skip = headerHash net.genesisHeader     } --- | Validate a list of continuous block headers and import them to the--- block chain. Return 'Left' on failure with error information.+-- | Validate a list of continuous block headers and import them into the+-- block chain. Each header is validated against the headers before it and+-- added to the store as it passes, so a failure keeps the headers that came+-- before it and makes the last of them the best block when it has the most+-- work. Return 'Left' on failure with error information, otherwise the new+-- nodes with the newest one first. connectBlocks ::   (BlockHeaders m) =>   Network ->@@ -291,48 +306,21 @@   [BlockHeader] ->   m (Either String [BlockNode]) connectBlocks _ _ [] = return $ Right []-connectBlocks net t bhs@(bh : _) =-  runExceptT $ do-    unless (chained bhs) $-      throwError "Blocks to connect do not form a chain"-    par <--      maybeToExceptT-        "Could not get parent block"-        (MaybeT (parentBlock bh))-    pars <- lift $ getParents 10 par-    bb <- lift getBestBlockHeader-    go par [] bb par pars bhs >>= \case-      bns@(bn : _) -> do-        lift $ addBlockHeaders bns-        let bb' = chooseBest bn bb-        when (bb' /= bb) $ lift $ setBestBlockHeader bb'-        return bns-      _ -> undefined+connectBlocks net t bhs+  | not (chained bhs) = return $ Left "Blocks to connect do not form a chain"+  | otherwise = do+      best <- getBestBlockHeader+      (result, nodes, best') <- go best [] bhs+      when (best' /= best) $ setBestBlockHeader best'+      return $ either Left (const (Right nodes)) result   where     chained (h1 : h2 : hs) = headerHash h1 == h2.prev && chained (h2 : hs)     chained _ = True-    skipit lbh ls par-      | sh == lbh.height = return lbh-      | sh < lbh.height = do-          skM <- lift $ getAncestor sh lbh-          case skM of-            Just sk -> return sk-            Nothing ->-              throwError $-                "BUG: Could not get skip for block "-                  ++ show (headerHash $ par.header)-      | otherwise = do-          let sn = ls !! fromIntegral (par.height - sh)-          when (sn.height /= sh) $-            throwError "BUG: Node height not right in skip"-          return sn-      where-        sh = skipHeight (par.height + 1)-    go _ acc _ _ _ [] = return acc-    go lbh acc bb par pars (h : hs) = do-      sk <- skipit lbh acc par-      bn <- ExceptT . return $ validBlock net t bb par pars h sk-      go lbh (bn : acc) (chooseBest bn bb) bn (take 10 $ par : pars) hs+    go best nodes [] = return (Right (), nodes, best)+    go best nodes (bh : rest) =+      connectHeader net t best bh >>= \case+        Left err -> return (Left err, nodes, best)+        Right node -> go (chooseBest best node) (node : nodes) rest  -- | Block's parent. If the block header is in the store, its parent must also -- be there. No block header get deleted or pruned from the store.@@ -353,27 +341,42 @@   m (Either String BlockNode) connectBlock net t bh =   runExceptT $ do+    best <- lift getBestBlockHeader+    node <- ExceptT $ connectHeader net t best bh+    let best' = chooseBest best node+    when (best' /= best) $ lift $ setBestBlockHeader best'+    return node++-- | Validate one header against the store and add it to the store. The best+-- block passed in is only used by the checkpoint rule and is not updated.+connectHeader ::+  (BlockHeaders m) =>+  Network ->+  Timestamp ->+  BlockNode ->+  BlockHeader ->+  m (Either String BlockNode)+connectHeader net t best bh =+  runExceptT $ do     par <-       maybeToExceptT         "Could not get parent block"         (MaybeT (parentBlock bh))     pars <- lift $ getParents 10 par-    skM <- lift $ getAncestor (skipHeight (par.height + 1)) par-    sk <--      case skM of+    skipNode <-+      lift (getAncestor (skipHeight (par.height + 1)) par) >>= \case         Just sk -> return sk         Nothing ->           throwError $             "BUG: Could not get skip for block "-              ++ show (headerHash $ par.header)-    bb <- lift getBestBlockHeader-    bn <- ExceptT . return $ validBlock net t bb par pars bh sk-    let bb' = chooseBest bb bn-    lift $ addBlockHeader bn-    when (bb /= bb') . lift $ setBestBlockHeader bb'-    return bn+              ++ show (headerHash par.header)+    requiredBits <- lift $ nextWorkRequired net par bh+    node <- ExceptT . return $ validBlock net t best par pars bh skipNode requiredBits+    lift $ addBlockHeader node+    return node --- | Validate this block header. Build a 'BlockNode' if successful.+-- | Validate this block header. Build a 'BlockNode' if successful. The checks+-- follow @CheckBlockHeader@ and @ContextualCheckBlockHeader@ in Bitcoin Core. validBlock ::   Network ->   -- | current time@@ -388,8 +391,10 @@   BlockHeader ->   -- | skip node (black magic)   BlockNode ->+  -- | difficulty bits the chain requires, from 'nextWorkRequired'+  Word32 ->   Either String BlockNode-validBlock net t bb par pars bh sk = do+validBlock net t bb par pars bh sk requiredBits = do   let mt = medianTime . map (.header.timestamp) $ par : pars       nt = bh.timestamp       hh = headerHash bh@@ -399,10 +404,14 @@   unless (isValidPOW net bh) $     Left $       "Proof of work failed: " ++ show (headerHash bh)-  unless (nt <= t + 2 * 60 * 60) $+  unless (bh.bits == requiredBits) $     Left $+      "Incorrect difficulty bits: " ++ showBits bh.bits ++ ", expected " ++ showBits requiredBits+  unless (toInteger nt <= toInteger t + 2 * 60 * 60) $+    Left $       "Invalid header timestamp: " ++ show nt-  unless (nt >= mt) $+  -- The timestamp must be strictly above the median time past.+  unless (nt > mt) $     Left $       "Block timestamp too early: " ++ show nt   unless (validTimeWarp net ng par.header.timestamp nt) $@@ -427,14 +436,25 @@         work = aw,         skip = headerHash sk.header       }+  where+    showBits b = "0x" ++ showHex b "" --- | Return the median of all provided timestamps. Can be unsorted. Error on--- empty list.+-- | Return the median of all provided timestamps. Can be unsorted. For an+-- even count the upper of the two middle values is taken, as Bitcoin Core+-- does. Error on empty list. medianTime :: [Timestamp] -> Timestamp medianTime ts   | null ts = error "Cannot compute median time of empty header list"   | otherwise = sort ts !! (length ts `div` 2) +-- | Median time past of a block: the median of the timestamps of the block and+-- its ten closest ancestors, or fewer near genesis. This is+-- @GetMedianTimePast@ in Bitcoin Core.+mtp :: (BlockHeaders m) => BlockNode -> m Timestamp+mtp bn = do+  pars <- getParents 10 bn+  return $ medianTime $ map (.header.timestamp) (bn : pars)+ -- | Calculate the height of the skip (magic) block that corresponds to the -- given height. The block hash of the ancestor at that height will be placed on -- the 'BlockNode' structure to help locate ancestors at any height quickly.@@ -489,13 +509,9 @@   BlockHeight ->   Bool afterLastCP net bestHeight newChildHeight =-  case lM of-    Just l -> l < newChildHeight-    Nothing -> True-  where-    lM =-      listToMaybe . reverse $-        [c | (c, _) <- net.checkpoints, c <= bestHeight]+  case [c | (c, _) <- net.checkpoints, c <= bestHeight] of+    [] -> True+    reached -> maximum reached < newChildHeight  -- | This block should be at least version 2 (BIP34). Block height must be -- included in the coinbase transaction to prevent non-unique transaction@@ -508,11 +524,12 @@   BlockHash ->   Bool bip34 net height hsh-  | fst net.bip34Block == 0 = True-  | fst net.bip34Block == height = snd net.bip34Block == hsh-  | otherwise = True+  | height /= net.bip34Height = True+  | otherwise = maybe True (== hsh) net.bip34Hash --- | Check if the provided block height and version are valid.+-- | Check if the provided block height and version are valid. The version is+-- a signed 32-bit number in Bitcoin Core, so values with the top bit set are+-- negative and count as below version 2. validVersion ::   Network ->   -- | new child height@@ -521,10 +538,12 @@   Word32 ->   Bool validVersion net height version-  | version < 2 = height < fst net.bip34Block-  | version < 3 = height < net.bip66Height-  | version < 4 = height < net.bip65Height+  | signedVersion < 2 = height < net.bip34Height+  | signedVersion < 3 = height < net.bip66Height+  | signedVersion < 4 = height < net.bip65Height   | otherwise = True+  where+    signedVersion = fromIntegral version :: Int32  -- | BIP94 time warp rule: the first block of a retarget period may not be more -- than 600 seconds earlier than its parent.@@ -538,7 +557,8 @@   Timestamp ->   Bool validTimeWarp net height parent child-  | net.bip94 && height `mod` diffInterval net == 0 = child + 600 >= parent+  | net.bip94 && height `mod` diffInterval net == 0 =+      toInteger child + 600 >= toInteger parent   | otherwise = True  -- | Find last block with normal, as opposed to minimum difficulty (for test@@ -559,71 +579,73 @@       lastNoMinDiff net bn'     else return bn +-- | Test network rule: a block more than twice the target spacing after its+-- parent may be mined at the minimum difficulty.+minDiffGap :: Network -> BlockNode -> BlockHeader -> Bool+minDiffGap net par bh =+  toInteger bh.timestamp > toInteger par.header.timestamp + 2 * toInteger net.targetSpacing+ -- | Returns the work required on a block header given the previous block. This--- coresponds to @bitcoind@ function @GetNextWorkRequired@ in @main.cpp@.+-- corresponds to @GetNextWorkRequired@ in Bitcoin Core and Bitcoin Cash Node. nextWorkRequired ::   (BlockHeaders m) =>   Network ->   BlockNode ->   BlockHeader ->   m Word32-nextWorkRequired net par bh = do-  ma <- getAsertAnchor net-  case asert ma <|> daa <|> eda <|> pow of-    Just f -> f par bh-    Nothing -> error "Could not determine difficulty algorithm"+nextWorkRequired net par bh+  -- Bitcoin Cash Node never retargets on regtest, before picking an algorithm.+  | usesCashRules && net.powNoRetarget = return par.header.bits+  | otherwise =+      asertAnchor net par >>= \case+        Just anchor -> nextAsertWorkRequired net anchor par bh+        Nothing+          | activeAt net.daaHeight -> nextDaaWorkRequired net par bh+          | activeAt net.edaHeight -> nextEdaWorkRequired net par bh+          | otherwise -> nextPowWorkRequired net par bh   where-    asert ma = do-      anchor <- ma-      guard (par.height > anchor.height)-      return $ nextAsertWorkRequired net anchor-    daa = do-      daa_height <- net.daaHeight-      guard (par.height + 1 >= daa_height)-      return $ nextDaaWorkRequired net-    eda = do-      eda_height <- net.edaHeight-      guard (par.height + 1 >= eda_height)-      return $ nextEdaWorkRequired net-    pow = return $ nextPowWorkRequired net+    usesCashRules =+      isJust net.edaHeight || isJust net.daaHeight || isJust net.asertActivationTime+    activeAt = maybe False (\h -> par.height + 1 >= h)  -- | Find out the next amount of work required according to the Emergency--- Difficulty Adjustment (EDA) algorithm from Bitcoin Cash.+-- Difficulty Adjustment (EDA) algorithm from Bitcoin Cash. This corresponds to+-- @GetNextEDAWorkRequired@ in Bitcoin Cash Node. nextEdaWorkRequired ::   (BlockHeaders m) => Network -> BlockNode -> BlockHeader -> m Word32 nextEdaWorkRequired net par bh   | (par.height + 1) `mod` diffInterval net == 0 =       nextPowWorkRequired net par bh-  | mindiff = return (encodeCompact net.powLimit)-  | par.header.bits == encodeCompact net.powLimit =-      return (encodeCompact net.powLimit)+  | net.minDiffBlocks =+      if minDiffGap net par bh+        then return powLimitBits+        else (.header.bits) <$> lastNoMinDiff net par+  | par.header.bits == powLimitBits =+      return powLimitBits   | otherwise = do       par6 <- fromMaybe e1 <$> getAncestor (par.height - 6) par-      pars <- getParents 10 par-      pars6 <- getParents 10 par6-      let par6med =-            medianTime $ map (.header.timestamp) (par6 : pars6)-          parmed = medianTime $ map (.header.timestamp) (par : pars)-          mtp6 = parmed - par6med-      if mtp6 < 12 * 3600-        then return $ par.header.bits-        else-          return $-            let (diff, _) = decodeCompact par.header.bits-                ndiff = diff + (diff `shiftR` 2)-             in if net.powLimit > ndiff-                  then encodeCompact net.powLimit-                  else encodeCompact ndiff+      parMtp <- mtp par+      par6Mtp <- mtp par6+      -- If producing the last six blocks took less than twelve hours the+      -- difficulty stays. Otherwise the target grows by a quarter.+      if toInteger parMtp - toInteger par6Mtp < 12 * 3600+        then return par.header.bits+        else do+          let (target, _) = decodeCompact par.header.bits+              target' = target + (target `shiftR` 2)+          return $ encodeCompact (min net.powLimit target')   where-    mindiff = bh.timestamp > par.header.timestamp + net.targetSpacing * 2+    powLimitBits = encodeCompact net.powLimit     e1 = error "Could not get seventh ancestor of block"  -- | Find the next amount of work required according to the Difficulty--- Adjustment Algorithm (DAA) from Bitcoin Cash.+-- Adjustment Algorithm (DAA) from Bitcoin Cash. This corresponds to+-- @GetNextCashWorkRequired@ in Bitcoin Cash Node. The parent must be at or+-- above the difficulty interval height. nextDaaWorkRequired ::   (BlockHeaders m) => Network -> BlockNode -> BlockHeader -> m Word32 nextDaaWorkRequired net par bh-  | mindiff = return (encodeCompact net.powLimit)+  | net.minDiffBlocks && minDiffGap net par bh = return (encodeCompact net.powLimit)   | otherwise = do       unless (par.height >= diffInterval net) $         error "Block height below difficulty interval"@@ -636,15 +658,7 @@         else return $ encodeCompact nextTarget   where     e1 = error "Cannot get ancestor at parent - 144 height"-    mindiff = bh.timestamp > par.header.timestamp + net.targetSpacing * 2 -mtp :: (BlockHeaders m) => BlockNode -> m Timestamp-mtp bn-  | bn.height == 0 = return 0-  | otherwise = do-      pars <- getParents 11 bn-      return $ medianTime (map (.header.timestamp) pars)- firstGreaterOrEqual ::   (BlockHeaders m) =>   Network ->@@ -714,39 +728,64 @@ middleOf :: (Integral a) => a -> a -> a middleOf a b = a + ((b - a) `div` 2) --- TODO: Use known anchor after fork-getAsertAnchor :: (BlockHeaders m) => Network -> m (Maybe BlockNode)-getAsertAnchor net =+-- | Anchor block of the aserti3-2d algorithm for the chain that ends at the+-- given block: the earliest ancestor, or the block itself, whose median time+-- past reaches the activation time. The anchor is the last block whose+-- difficulty came from the previous algorithm. Returns 'Nothing' when the+-- block's own median time past is still below the activation time, in which+-- case the previous algorithm applies to its children.+asertAnchor :: (BlockHeaders m) => Network -> BlockNode -> m (Maybe BlockNode)+asertAnchor net tip =   case net.asertActivationTime of     Nothing -> return Nothing-    Just act -> firstGreaterOrEqual net (f act)+    Just activation -> do+      tipMtp <- mtp tip+      if tipMtp < activation+        then return Nothing+        else Just <$> search activation 0 tip.height   where-    f act bn = do-      m <- mtp bn-      return $ compare m act+    -- The median time past never decreases along a chain, so a binary search+    -- over the heights finds the first block that reaches the activation+    -- time. The upper bound always satisfies it.+    search activation lo hi+      | lo >= hi = ancestorAt lo+      | otherwise = do+          let mid = lo + (hi - lo) `div` 2+          midMtp <- mtp =<< ancestorAt mid+          if midMtp >= activation+            then search activation lo mid+            else search activation (mid + 1) hi+    ancestorAt h = fromMaybe e <$> getAncestor h tip+    e = error "asertAnchor: could not get ancestor" --- | Find the next amount of work required according to the aserti3-2d algorithm.+-- | Find the next amount of work required according to the aserti3-2d+-- algorithm. This corresponds to @GetNextASERTWorkRequired@ in Bitcoin Cash+-- Node. nextAsertWorkRequired ::   (BlockHeaders m) =>   Network ->+  -- | anchor block, see 'asertAnchor'   BlockNode ->+  -- | parent of the new block   BlockNode ->+  -- | new block header   BlockHeader ->   m Word32-nextAsertWorkRequired net anchor par bh = do-  anchor_parent <--    fromMaybe e_fork <$> getBlockHeader anchor.header.prev-  let anchor_parent_time = toInteger anchor_parent.header.timestamp-      time_diff = current_time - anchor_parent_time-  return $ computeAsertBits halflife anchor_bits time_diff height_diff+nextAsertWorkRequired net anchor par bh+  | net.minDiffBlocks && minDiffGap net par bh = return (encodeCompact net.powLimit)+  | otherwise = do+      -- The clock starts at the anchor's parent. A genesis anchor has no+      -- parent, so its own timestamp is used, as Bitcoin Cash Node does.+      anchorParentTime <-+        if isGenesis anchor+          then return anchor.header.timestamp+          else (.header.timestamp) . fromMaybe e <$> getBlockHeader anchor.header.prev+      let timeDiff = toInteger bh.timestamp - toInteger anchorParentTime+          heightDiff = toInteger par.height + 1 - toInteger anchor.height+      return $+        computeAsertBits net.powLimit net.asertHalfLife anchor.header.bits timeDiff heightDiff   where-    halflife = net.asertHalfLife-    anchor_height = toInteger anchor.height-    anchor_bits = anchor.header.bits-    current_height = toInteger par.height + 1-    height_diff = current_height - anchor_height-    current_time = toInteger bh.timestamp-    e_fork = error "Could not get fork block header"+    e = error "Could not get parent of ASERT anchor block"  idealBlockTime :: Integer idealBlockTime = 10 * 60@@ -757,68 +796,84 @@ radix :: Integer radix = 1 `shiftL` rBits -maxBits :: Word32-maxBits = 0x1d00ffff--maxTarget :: Integer-maxTarget = fst $ decodeCompact maxBits-+-- | The aserti3-2d formula: the target of the anchor block, doubled for every+-- half-life that the chain runs behind its ideal schedule and halved for every+-- half-life it runs ahead, in 16.16 fixed point with a cubic approximation of+-- the exponential. computeAsertBits ::+  -- | proof-of-work limit (largest target) of the network   Integer ->+  -- | half-life in seconds+  Integer ->+  -- | bits of the anchor block   Word32 ->+  -- | seconds from the anchor's parent to the new block   Integer ->+  -- | blocks from the anchor to the new block   Integer ->   Word32-computeAsertBits halflife anchor_bits time_diff height_diff =-  if e2 >= 0 && e2 < 65536-    then-      if g4 == 0-        then encodeCompact 1-        else-          if g4 > maxTarget-            then maxBits-            else encodeCompact g4-    else error $ "Exponent not in range: " ++ show e2+computeAsertBits powLimit halflife anchorBits timeDiff heightDiff+  | target == 0 = encodeCompact 1+  | target > powLimit = encodeCompact powLimit+  | otherwise = encodeCompact target   where-    g1 = fst (decodeCompact anchor_bits)-    e1 =-      ((time_diff - idealBlockTime * (height_diff + 1)) * radix)-        `quot` halflife-    s = e1 `shiftR` rBits-    e2 = e1 - s * radix-    g2 =-      g1-        * ( radix-              + ( (195766423245049 * e2 + 971821376 * e2 ^ 2 + 5127 * e2 ^ 3 + 2 ^ 47)-                    `shiftR` (rBits * 3)-                )+    anchorTarget = fst (decodeCompact anchorBits)+    -- Exponent in 16.16 fixed point. The reference C++ code truncates toward+    -- zero, hence quot.+    expo = ((timeDiff - idealBlockTime * (heightDiff + 1)) * radix) `quot` halflife+    -- Whole half-lives (an arithmetic shift, so rounded down) and the+    -- fractional part in [0, radix).+    shifts = expo `shiftR` rBits+    frac = expo - shifts * radix+    -- 2 ^ (frac / radix) in 16.16 fixed point.+    factor =+      radix+        + ( (195766423245049 * frac + 971821376 * frac ^ (2 :: Int) + 5127 * frac ^ (3 :: Int) + 2 ^ (47 :: Int))+              `shiftR` (rBits * 3)           )-    g3 =-      if s < 0-        then g2 `shiftR` negate (fromIntegral s)-        else g2 `shiftL` fromIntegral s-    g4 = g3 `shiftR` rBits+    scaled = anchorTarget * factor+    shifted+      | shifts < 0 = scaled `shiftR` fromInteger (negate shifts)+      | otherwise = scaled `shiftL` fromInteger shifts+    target = shifted `shiftR` rBits --- | Compute Bitcoin Cash DAA target for a new block.-computeTarget :: Network -> BlockNode -> BlockNode -> Integer-computeTarget net f l =-  let work = (l.work - f.work) * fromIntegral net.targetSpacing-      tspan = l.header.timestamp - f.header.timestamp-      tspan'-        | tspan > 288 * net.targetSpacing =-            288 * net.targetSpacing-        | tspan < 72 * net.targetSpacing =-            72 * net.targetSpacing-        | otherwise = tspan-      work' = work `div` fromIntegral tspan'-   in 2 ^ (256 :: Integer) `div` work'+-- | Compute Bitcoin Cash DAA target for a new block, like @ComputeTarget@ in+-- Bitcoin Cash Node.+computeTarget ::+  Network ->+  -- | first suitable block of the window+  BlockNode ->+  -- | last suitable block of the window+  BlockNode ->+  Integer+computeTarget net firstBlock lastBlock =+  let spacing = toInteger net.targetSpacing+      work = (lastBlock.work - firstBlock.work) * spacing+      actual = toInteger lastBlock.header.timestamp - toInteger firstBlock.header.timestamp+      clamped = max (72 * spacing) (min (288 * spacing) actual)+      work' = work `div` clamped+   in -- (2^256 - work) / work, as the unsigned negation in the reference code+      (2 ^ (256 :: Int) - work') `div` work' --- | Get suitable block for Bitcoin Cash DAA computation.+-- | Get suitable block for Bitcoin Cash DAA computation: the block with the+-- median timestamp among the given block, its parent and its grandparent. On+-- equal timestamps the same block as Bitcoin Cash Node's sorting network is+-- chosen. getSuitableBlock :: (BlockHeaders m) => BlockNode -> m BlockNode getSuitableBlock par = do   unless (par.height >= 3) $ error "Block height is less than three"-  blocks <- (par :) <$> getParents 2 par-  return $ sortBy (compare `on` (.header.timestamp)) blocks !! 1+  parents <- getParents 2 par+  case parents of+    [parent, grandparent] -> return $ medianOfThree grandparent parent par+    _ -> error "Could not get the two parents of the block"+  where+    later x y = x.header.timestamp > y.header.timestamp+    -- Oldest first, swapping only on a strictly later timestamp.+    medianOfThree b0 b1 b2 =+      let (c0, c2) = if later b0 b2 then (b2, b0) else (b0, b2)+          (d0, d1) = if later c0 b1 then (b1, c0) else (c0, b1)+          (e1, _) = if later d1 c2 then (c2, d1) else (d1, c2)+       in const e1 d0  -- | Returns the work required on a block header given the previous block. This -- coresponds to bitcoind function GetNextWorkRequired in main.cpp.@@ -828,7 +883,7 @@   | (par.height + 1) `mod` diffInterval net /= 0 =       if net.minDiffBlocks         then-          if ht > pt + delta+          if minDiffGap net par bh             then return $ encodeCompact net.powLimit             else do               d <- lastNoMinDiff net par@@ -845,11 +900,11 @@       return $ calcNextWork net h f.header.timestamp   where     e1 = error "Could not get ancestor for block header"-    pt = par.header.timestamp-    ht = bh.timestamp-    delta = net.targetSpacing * 2  -- | Computes the work required for the first block in a new retarget period.+-- The actual timespan is limited to a quarter and four times the target+-- timespan. A negative timespan, when the last block is dated before the+-- first, is limited like a short one. calcNextWork ::   Network ->   -- | last block in previous retarget (parent)@@ -857,18 +912,15 @@   -- | timestamp of first block in previous retarget   Timestamp ->   Word32-calcNextWork net header time+calcNextWork net header firstTime   | net.powNoRetarget = header.bits   | new > net.powLimit = encodeCompact net.powLimit   | otherwise = encodeCompact new   where-    s = header.timestamp - time-    n-      | s < net.targetTimespan `div` 4 = net.targetTimespan `div` 4-      | s > net.targetTimespan * 4 = net.targetTimespan * 4-      | otherwise = s-    l = fst $ decodeCompact header.bits-    new = l * fromIntegral n `div` fromIntegral net.targetTimespan+    timespan = toInteger net.targetTimespan+    actual = toInteger header.timestamp - toInteger firstTime+    clamped = max (timespan `div` 4) (min (timespan * 4) actual)+    new = fst (decodeCompact header.bits) * clamped `div` timespan  -- | Returns True if the difficulty target (bits) of the header is valid and the -- proof of work of the header matches the advertised difficulty target. This@@ -887,18 +939,24 @@  -- | Returns the work represented by this block. Work is defined as the number -- of tries needed to solve a block in the average case with respect to the--- target.+-- target. A header whose bits are negative, overflow or are zero has no work,+-- like @GetBlockProof@ in Bitcoin Core. headerWork :: BlockHeader -> Integer-headerWork bh = largestHash `div` (target + 1)+headerWork bh+  | target <= 0 || over = 0+  | otherwise = largestHash `div` (target + 1)   where-    target = fst $ decodeCompact bh.bits+    (target, over) = decodeCompact bh.bits     largestHash = 1 `shiftL` 256  -- | Number of blocks on average between difficulty cycles (2016 blocks). diffInterval :: Network -> Word32 diffInterval net = net.targetTimespan `div` net.targetSpacing --- | Compare two blocks to get the best.+-- | Compare two blocks to get the best: the one with more accumulated work,+-- then the higher one. When both are equal the first argument is kept, so+-- callers pass the block they already have first and the block that was seen+-- first stays the best. chooseBest :: BlockNode -> BlockNode -> BlockNode chooseBest b1 b2   | b1.work == b2.work =@@ -908,26 +966,23 @@   | b1.work > b2.work = b1   | otherwise = b2 --- | Get list of blocks for a block locator.+-- | Get list of blocks for a block locator: the ten blocks below the best, then+-- blocks at exponentially growing distances, then genesis. This is+-- @LocatorEntries@ in Bitcoin Core. blockLocatorNodes :: (BlockHeaders m) => BlockNode -> m [BlockNode] blockLocatorNodes best =-  reverse <$> go [] best 1+  reverse <$> go [] (0 :: Int) best 1   where     e1 = error "Could not get ancestor"-    go loc bn n =-      let loc' = bn : loc-          n' =-            if length loc' > 10-              then n * 2-              else 1-       in if bn.height < n'-            then do-              a <- fromMaybe e1 <$> getAncestor 0 bn-              return $ a : loc'-            else do-              let h = bn.height - n'-              bn' <- fromMaybe e1 <$> getAncestor h bn-              go loc' bn' n'+    go acc count node step+      | isGenesis node = return (node : acc)+      | otherwise = do+          let acc' = node : acc+              count' = count + 1+              height = if node.height > step then node.height - step else 0+              step' = if count' > 10 then step * 2 else step+          ancestor <- fromMaybe e1 <$> getAncestor height node+          go acc' count' ancestor step'  -- | Get block locator. blockLocator :: (BlockHeaders m) => BlockNode -> m BlockLocator@@ -936,14 +991,13 @@ -- | Become rich beyond your wildest dreams. mineBlock :: Network -> Word32 -> BlockHeader -> BlockHeader mineBlock net seed h =-  head-    [ j-    | i <- (+ seed) <$> [0 .. maxBound],-      let j = h {nonce = i},-      isValidPOW net j-    ]+  case filter (isValidPOW net) [h {nonce = i} | i <- (+ seed) <$> [0 .. maxBound]] of+    (mined : _) -> mined+    [] -> error "mineBlock: no nonce satisfies the target"  -- | Generate and append new blocks (mining). Only practical in regtest network.+-- Every block is dated one target spacing after its parent, so the chain+-- passes the median time past rule. appendBlocks ::   Network ->   -- | random seed@@ -960,7 +1014,11 @@         net         seed         bh-          { prev = headerHash bh,+          { -- Version 4 passes the version rules of BIP34, BIP65 and BIP66+            -- at any height. Regtest applies them from height 1.+            version = 4,+            prev = headerHash bh,+            timestamp = bh.timestamp + net.targetSpacing,             -- Just to make it different in every header             merkle = sha256 $ runPutS $ serialize seed           }@@ -984,8 +1042,8 @@           f pl pr  -- | Generate the entire Genesis block for 'Network'.-genesisBlock :: Network -> Ctx -> Block-genesisBlock net ctx = Block net.genesisHeader [genesisTx ctx]+genesisBlock :: Network -> Block+genesisBlock net = Block net.genesisHeader [net.genesisTx]  -- | Compute block subsidy at particular height. computeSubsidy :: Network -> BlockHeight -> Word64
src/Haskoin/Block/Merkle.hs view
@@ -23,32 +23,29 @@     merkleBlockTxs,     testMerkleRoot,     buildPartialMerkle,+    extractMatches,     decodeMerkleFlags,     encodeMerkleFlags,     calcTreeHeight,     calcTreeWidth,     hash2,     calcHash,-    traverseAndBuild,-    traverseAndExtract,-    extractMatches,     splitIn,     boolsToWord8,   ) where  import Control.DeepSeq-import Control.Monad (forM_, replicateM, when)+import Control.Monad (when) import Data.Binary (Binary (..)) import Data.Bits-import qualified Data.ByteString as BS import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Either (isRight) import Data.Hashable-import Data.Maybe import Data.Serialize (Serialize (..))+import qualified Data.Vector as V import Data.Word (Word32, Word8) import GHC.Generics import Haskoin.Block.Common@@ -84,20 +81,15 @@   deserialize = do     header <- deserialize     ntx <- getWord32le-    (VarInt matchLen) <- deserialize-    hashes <- replicateM (fromIntegral matchLen) deserialize-    (VarInt flagLen) <- deserialize-    ws <- replicateM (fromIntegral flagLen) getWord8-    return $ MerkleBlock header ntx hashes (decodeMerkleFlags ws)+    hashes <- getVarIntList deserialize+    flagBytes <- getVarIntList getWord8+    return $ MerkleBlock header ntx hashes (decodeMerkleFlags flagBytes)    serialize (MerkleBlock h ntx hashes flags) = do     serialize h     putWord32le ntx-    putVarInt $ length hashes-    forM_ hashes serialize-    let ws = encodeMerkleFlags flags-    putVarInt $ length ws-    forM_ ws putWord8+    putVarIntList serialize hashes+    putVarIntList putWord8 (encodeMerkleFlags flags)  instance Binary MerkleBlock where   put = serialize@@ -107,13 +99,12 @@   put = serialize   get = deserialize --- | Unpack Merkle flags into 'FlagBits' structure.+-- | Unpack Merkle flags into 'FlagBits' structure. The least significant bit+-- of each byte comes first. decodeMerkleFlags :: [Word8] -> FlagBits-decodeMerkleFlags ws =-  [ b | p <- [0 .. length ws * 8 - 1], b <- [testBit (ws !! (p `div` 8)) (p `mod` 8)]-  ]+decodeMerkleFlags = concatMap (\w -> map (testBit w) [0 .. 7]) --- | Pack Merkle flags from 'FlagBits'.+-- | Pack Merkle flags from 'FlagBits'. The last byte is padded with zero bits. encodeMerkleFlags :: FlagBits -> [Word8] encodeMerkleFlags bs = map boolsToWord8 $ splitIn 8 bs @@ -139,19 +130,51 @@   Int calcTreeWidth ntx h = (ntx + (1 `shiftL` h) - 1) `shiftR` h --- | Computes the root of a Merkle tree from a list of leaf node hashes.+-- | Every level of a Merkle tree, leaves first and root last. Each level+-- lists its nodes from left to right. A level with an odd number of nodes+-- pairs its last node with itself to build the level above.+type MerkleLevels = [V.Vector Hash256]++-- | Build all levels of the Merkle tree over the given leaves, in linear+-- time. Also report whether some node combines two identical hashes, which+-- Bitcoin Core calls a mutated tree (CVE-2012-2459). The last node of an odd+-- level pairing with itself does not count as a duplicate.+merkleLevels :: [Hash256] -> (MerkleLevels, Bool)+merkleLevels leaves = go (V.fromList leaves) False []+  where+    go level mutated above+      | V.length level <= 1 = (reverse (level : above), mutated)+      | otherwise = go (V.fromList parents) (mutated || duplicated) (level : above)+      where+        count = V.length level+        pairs = [(level V.! i, level V.! min (i + 1) (count - 1)) | i <- [0, 2 .. count - 1]]+        parents = [hash2 l r | (l, r) <- pairs]+        duplicated = or [level V.! i == level V.! (i + 1) | i <- [0, 2 .. count - 2]]++-- | Computes the root of a Merkle tree from a list of leaf node hashes. Fails+-- for an empty list, since a block has at least its coinbase, and for a+-- mutated tree, in which two sibling nodes are identical (CVE-2012-2459). A+-- block with duplicate transactions is invalid, and its mutated tree has the+-- same root as the valid block it was derived from. buildMerkleRoot ::   -- | transaction hashes (leaf nodes)   [TxHash] ->   -- | root of the Merkle tree-  MerkleRoot-buildMerkleRoot txs = calcHash (calcTreeHeight $ length txs) 0 txs+  Either String MerkleRoot+buildMerkleRoot [] = Left "buildMerkleRoot: a block needs at least one transaction"+buildMerkleRoot txs+  | mutated = Left "buildMerkleRoot: duplicate transaction hashes (mutated tree)"+  | otherwise = Right (V.head (last levels))+  where+    (levels, mutated) = merkleLevels (map (.get) txs)  -- | Concatenate and compute double SHA256. hash2 :: Hash256 -> Hash256 -> Hash256 hash2 a b = doubleSHA256 $ runPutS (serialize a) <> runPutS (serialize b) --- | Computes the hash of a specific node in a Merkle tree.+-- | Computes the hash of a specific node in a Merkle tree. Builds the whole+-- tree, so use 'buildMerkleRoot' or 'buildPartialMerkle' for more than one+-- node. calcHash ::   -- | height of the node   Int ->@@ -163,14 +186,9 @@   Hash256 calcHash height pos txs   | height < 0 || pos < 0 = error "calcHash: Invalid parameters"-  | height == 0 = (txs !! pos).get-  | otherwise = hash2 left right+  | otherwise = (levels !! height) V.! pos   where-    left = calcHash (height - 1) (pos * 2) txs-    right-      | pos * 2 + 1 < calcTreeWidth (length txs) (height - 1) =-          calcHash (height - 1) (pos * 2 + 1) txs-      | otherwise = left+    (levels, _) = merkleLevels (map (.get) txs)  -- | Build a partial Merkle tree. Provide a list of tuples with all transaction -- hashes in the block, and whether the transaction is to be included in the@@ -181,61 +199,74 @@   [(TxHash, Bool)] ->   -- | flag bits and partial Merkle tree   (FlagBits, PartialMerkleTree)-buildPartialMerkle hs = traverseAndBuild (calcTreeHeight $ length hs) 0 hs+buildPartialMerkle [] = ([], [])+buildPartialMerkle txs = traverseAndBuild levels matches (length levels - 1) 0+  where+    (levels, _) = merkleLevels (map ((.get) . fst) txs)+    matches = matchLevels (map snd txs) --- | Helper function to build partial Merkle tree. Used by 'buildPartialMerkle'--- above.+-- | For every level of the tree, whether each node has a matching transaction+-- somewhere below it. Shaped like the levels of 'merkleLevels'.+matchLevels :: [Bool] -> [V.Vector Bool]+matchLevels leafMatches = go (V.fromList leafMatches) []+  where+    go level above+      | V.length level <= 1 = reverse (level : above)+      | otherwise = go (V.fromList parents) (level : above)+      where+        count = V.length level+        parents = [level V.! i || level V.! min (i + 1) (count - 1) | i <- [0, 2 .. count - 1]]++-- | Depth-first walk that records the partial Merkle tree, like+-- @CPartialMerkleTree::TraverseAndBuild@ in Bitcoin Core. A node with no match+-- below it is stored as a single hash. A node with a match is descended into. traverseAndBuild ::-  Int -> Int -> [(TxHash, Bool)] -> (FlagBits, PartialMerkleTree)-traverseAndBuild height pos txs-  | height < 0 || pos < 0 = error "traverseAndBuild: Invalid parameters"-  | height == 0 || not match = ([match], [calcHash height pos t])-  | otherwise = (match : lb ++ rb, lh ++ rh)+  MerkleLevels -> [V.Vector Bool] -> Int -> Int -> (FlagBits, PartialMerkleTree)+traverseAndBuild levels matches height pos+  | height == 0 || not match = ([match], [node])+  | otherwise = (match : leftBits ++ rightBits, leftHashes ++ rightHashes)   where-    t = map fst txs-    s = pos `shiftL` height-    e = min (length txs) $ (pos + 1) `shiftL` height-    match = any snd $ take (e - s) $ drop s txs-    (lb, lh) = traverseAndBuild (height - 1) (pos * 2) txs-    (rb, rh)-      | (pos * 2 + 1) < calcTreeWidth (length txs) (height - 1) =-          traverseAndBuild (height - 1) (pos * 2 + 1) txs+    match = (matches !! height) V.! pos+    node = (levels !! height) V.! pos+    (leftBits, leftHashes) = traverseAndBuild levels matches (height - 1) (pos * 2)+    (rightBits, rightHashes)+      | pos * 2 + 1 < V.length (levels !! (height - 1)) =+          traverseAndBuild levels matches (height - 1) (pos * 2 + 1)       | otherwise = ([], []) --- | Helper function to extract transaction hashes from partial Merkle tree.+-- | Depth-first walk that rebuilds a node of the partial Merkle tree, like+-- @CPartialMerkleTree::TraverseAndExtract@ in Bitcoin Core. Returns the hash+-- of the node, the matching transactions below it and the flags and hashes+-- that are still unused. traverseAndExtract ::   Int ->   Int ->   Int ->   FlagBits ->   PartialMerkleTree ->-  Maybe (MerkleRoot, [TxHash], Int, Int)-traverseAndExtract height pos ntx flags hashes-  | null flags = Nothing-  | height == 0 || not match = leafResult-  | isNothing leftM = Nothing-  | (pos * 2 + 1) >= calcTreeWidth ntx (height - 1) =-      Just (hash2 lh lh, lm, lcf + 1, lch)-  | isNothing rightM = Nothing-  | otherwise =-      Just (hash2 lh rh, lm ++ rm, lcf + rcf + 1, lch + rch)-  where-    leafResult-      | null hashes = Nothing-      | otherwise = Just (h, [TxHash h | height == 0 && match], 1, 1)-    (match : fs) = flags-    (h : _) = hashes-    leftM = traverseAndExtract (height - 1) (pos * 2) ntx fs hashes-    (lh, lm, lcf, lch) = fromMaybe e leftM-    rightM =-      traverseAndExtract-        (height - 1)-        (pos * 2 + 1)-        ntx-        (drop lcf fs)-        (drop lch hashes)-    (rh, rm, rcf, rch) = fromMaybe e rightM-    e = error "traverseAndExtract: unexpected error extracting a Maybe value"+  Either String (Hash256, [TxHash], FlagBits, PartialMerkleTree)+traverseAndExtract height pos ntx flags hashes =+  case flags of+    [] -> Left "extractMatches: ran out of flag bits"+    (parentOfMatch : flags')+      | height == 0 || not parentOfMatch ->+          -- a leaf, or a subtree without matches: its hash is stored as is+          case hashes of+            [] -> Left "extractMatches: ran out of hashes"+            (h : hashes') ->+              Right (h, [TxHash h | height == 0 && parentOfMatch], flags', hashes')+      | otherwise -> do+          (left, leftMatches, flags'', hashes'') <-+            traverseAndExtract (height - 1) (pos * 2) ntx flags' hashes+          if pos * 2 + 1 < calcTreeWidth ntx (height - 1)+            then do+              (right, rightMatches, flags''', hashes''') <-+                traverseAndExtract (height - 1) (pos * 2 + 1) ntx flags'' hashes''+              -- Two branches never hash the same set of distinct transactions.+              when (left == right) $+                Left "extractMatches: identical left and right branches (mutated tree)"+              return (hash2 left right, leftMatches ++ rightMatches, flags''', hashes''')+            else return (hash2 left left, leftMatches, flags'', hashes'')  -- | Extracts the matching hashes from a partial merkle tree. This will return -- the list of transaction hashes that have been included (set to true) in@@ -250,31 +281,23 @@   Either String (MerkleRoot, [TxHash]) extractMatches net flags hashes ntx   | ntx == 0 =-      Left-        "extractMatches: number of transactions can not be 0"+      Left "extractMatches: number of transactions can not be 0"   | ntx > net.maxBlockSize `div` 60 =-      Left-        "extractMatches: number of transactions excessively high"+      Left "extractMatches: number of transactions excessively high"   | length hashes > ntx =-      Left-        "extractMatches: More hashes provided than the number of transactions"+      Left "extractMatches: More hashes provided than the number of transactions"   | length flags < length hashes =-      Left-        "extractMatches: At least one bit per node and one bit per hash"-  | isNothing resM =-      Left-        "extractMatches: traverseAndExtract failed"-  | (nBitsUsed + 7) `div` 8 /= (length flags + 7) `div` 8 =-      Left-        "extractMatches: All bits were not consumed"-  | nHashUsed /= length hashes =-      Left $-        "extractMatches: All hashes were not consumed: " ++ show nHashUsed-  | otherwise = return (merkRoot, matches)-  where-    resM = traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes-    (merkRoot, matches, nBitsUsed, nHashUsed) = fromMaybe e resM-    e = error "extractMatches: unexpected error extracting a Maybe value"+      Left "extractMatches: At least one bit per node and one bit per hash"+  | otherwise = do+      (root, matches, unusedFlags, unusedHashes) <-+        traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes+      let bitsUsed = length flags - length unusedFlags+      -- Only the padding of the last flag byte may stay unused.+      when ((bitsUsed + 7) `div` 8 /= (length flags + 7) `div` 8) $+        Left "extractMatches: All bits were not consumed"+      when (not (null unusedHashes)) $+        Left "extractMatches: All hashes were not consumed"+      return (root, matches)  -- | Helper function to split a list in chunks 'Int' length. Last chunk may be -- smaller.
src/Haskoin/Crypto/Hash.hs view
@@ -40,6 +40,7 @@ where  import Control.DeepSeq+import Control.Monad (guard) import Crypto.Hash import Crypto.MAC.HMAC (HMAC, hmac) import Data.Binary (Binary (..))@@ -82,6 +83,15 @@   put = serialize   get = deserialize +-- | Decode a hex string that must hold exactly the given number of bytes. The+-- text forms of the hash types use it so that a hash can never have the wrong+-- length.+decodeHexBytes :: Int -> String -> Maybe ByteString+decodeHexBytes n str = do+  bs <- decodeHex (cs str)+  guard (B.length bs == n)+  return bs+ -- | Type for 512-bit hashes. newtype Hash512 = Hash512 {get :: ShortByteString}   deriving (Eq, Ord, Generic)@@ -103,7 +113,7 @@ instance Read Hash512 where   readPrec = do     R.String str <- lexP-    maybe pfail (return . Hash512 . toShort) (decodeHex (cs str))+    maybe pfail (return . Hash512 . toShort) (decodeHexBytes 64 str)  instance Show Hash256 where   showsPrec _ = shows . encodeHex . fromShort . (.get)@@ -111,7 +121,7 @@ instance Read Hash256 where   readPrec = do     R.String str <- lexP-    maybe pfail (return . Hash256 . toShort) (decodeHex (cs str))+    maybe pfail (return . Hash256 . toShort) (decodeHexBytes 32 str)  instance Show Hash160 where   showsPrec _ = shows . encodeHex . fromShort . (.get)@@ -119,16 +129,10 @@ instance Read Hash160 where   readPrec = do     R.String str <- lexP-    maybe pfail (return . Hash160 . toShort) (decodeHex (cs str))+    maybe pfail (return . Hash160 . toShort) (decodeHexBytes 20 str)  instance IsString Hash512 where-  fromString str =-    case decodeHex $ cs str of-      Nothing -> e-      Just bs ->-        case B.length bs of-          64 -> Hash512 (toShort bs)-          _ -> e+  fromString = maybe e (Hash512 . toShort) . decodeHexBytes 64     where       e = error "Could not decode hash from hex string" @@ -145,13 +149,7 @@   get = deserialize  instance IsString Hash256 where-  fromString str =-    case decodeHex $ cs str of-      Nothing -> e-      Just bs ->-        case B.length bs of-          32 -> Hash256 (toShort bs)-          _ -> e+  fromString = maybe e (Hash256 . toShort) . decodeHexBytes 32     where       e = error "Could not decode hash from hex string" @@ -168,13 +166,7 @@   get = deserialize  instance IsString Hash160 where-  fromString str =-    case decodeHex $ cs str of-      Nothing -> e-      Just bs ->-        case B.length bs of-          20 -> Hash160 (toShort bs)-          _ -> e+  fromString = maybe e (Hash160 . toShort) . decodeHexBytes 20     where       e = error "Could not decode hash from hex string" 
src/Haskoin/Crypto/Keys/Common.hs view
@@ -25,8 +25,10 @@     PublicKey (..),     PrivateKey (..),     wrapPubKey,+    decodePubKey,     derivePublicKey,     wrapSecKey,+    decodeSecKey,     fromMiniKey,     tweakPubKey,     tweakSecKey,@@ -38,7 +40,7 @@ where  import Control.DeepSeq-import Control.Monad (guard, mzero, (<=<))+import Control.Monad (guard, mzero, unless, (<=<)) import Crypto.Secp256k1 import Data.Aeson   ( Encoding,@@ -64,6 +66,7 @@ import Data.Serialize (Serialize (..)) import Data.String (IsString, fromString) import Data.String.Conversions (cs)+import Data.Text qualified as T import GHC.Generics (Generic) import Haskoin.Address.Base58 import Haskoin.Crypto.Hash@@ -85,7 +88,7 @@   unmarshalValue ctx =     withText "PublicKey" $ \t -> do       bs <- maybe (fail "Expected hex public key") return $ decodeHex t-      either fail return $ unmarshal ctx bs+      either fail return $ decodePubKey ctx bs  instance Marshal Ctx PublicKey where   marshalGet ctx = do@@ -108,6 +111,18 @@ wrapPubKey :: Bool -> PubKey -> PublicKey wrapPubKey c p = PublicKey p c +-- | Decode a public key from bytes that must hold the key and nothing else:+-- 33 bytes starting with 0x02 or 0x03, or 65 bytes starting with 0x04. The+-- 'Marshal' instance reads a key from a longer stream and leaves the rest, so+-- it would accept trailing bytes here. A key pushed in a script, a witness or+-- a PSBT record is the whole item, so use this function for those.+decodePubKey :: Ctx -> ByteString -> Either String PublicKey+decodePubKey ctx = runGetS $ do+  k <- marshalGet ctx+  done <- isEmpty+  unless done $ fail "Trailing bytes after public key"+  return k+ -- | Derives a public key from a private key. This function will preserve -- compression flag. derivePublicKey :: Ctx -> PrivateKey -> PublicKey@@ -126,16 +141,29 @@   { key :: !SecKey,     compress :: !Bool   }-  deriving (Eq, Show, Read, Generic, NFData)+  deriving (Eq, Generic, NFData) +-- | The secret is not shown. A key that ends up in a log, an error message+-- or a REPL session must not give its coins away. There is no 'Read'+-- instance for the same reason: use 'toWif' and 'fromWif' to write a key+-- out and read it back.+instance Show PrivateKey where+  showsPrec d p =+    showParen (d > 10) $+      showString "PrivateKey {key = <redacted>, compress = "+        . shows p.compress+        . showString "}"+ instance Serial PrivateKey where   serialize p = do     putByteString p.key.get     serialize p.compress   deserialize = do-    k <- getByteString 32+    bs <- getByteString 32     c <- deserialize-    return PrivateKey {key = SecKey k, compress = c}+    case decodeSecKey bs of+      Nothing -> fail "Secret key is zero or not below the curve order"+      Just k -> return PrivateKey {key = k, compress = c}  instance MarshalJSON Network PrivateKey where   marshalValue net = String . toWif net@@ -148,6 +176,20 @@ wrapSecKey :: Bool -> SecKey -> PrivateKey wrapSecKey c d = PrivateKey d c +-- | Order of the secp256k1 group. A secret key is a number in @[1, n-1]@.+curveOrder :: Integer+curveOrder = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141++-- | Build a secret key from its 32 big-endian bytes. Fails when the value is+-- zero or not below the curve order. 'Crypto.Secp256k1.secKey' only checks the+-- length, and such a key would make 'derivePubKey' and 'signMsg' crash.+decodeSecKey :: ByteString -> Maybe SecKey+decodeSecKey bs = do+  guard (BS.length bs == 32)+  let n = bsToInteger bs+  guard (n >= 1 && n < curveOrder)+  secKey bs+ -- | Tweak a private key. tweakSecKey :: Ctx -> SecKey -> Hash256 -> Maybe SecKey tweakSecKey ctx k =@@ -157,24 +199,35 @@ fromMiniKey :: ByteString -> Maybe PrivateKey fromMiniKey bs = do   guard checkShortKey-  wrapSecKey False <$> (secKey . runPutS . serialize . sha256) bs+  wrapSecKey False <$> (decodeSecKey . runPutS . serialize . sha256) bs   where     checkHash = runPutS $ serialize $ sha256 $ bs `BS.append` "?"     checkShortKey = BS.length bs `elem` [22, 30] && BS.head checkHash == 0x00 --- | Decode private key from WIF (wallet import format) string.+-- | Longest Base58 string a WIF can take. A WIF holds at most 38 bytes with+-- its checksum, which never need more than 52 digits because 58^52 is larger+-- than 256^38. Longer strings are rejected before decoding, since the time to+-- decode Base58 grows with the square of the length.+maxWifLength :: Int+maxWifLength = 52++-- | Decode private key from WIF (wallet import format) string. Fails on a+-- string of another network, of the wrong length, or holding a key that is+-- zero or not below the curve order. fromWif :: Network -> Base58 -> Maybe PrivateKey fromWif net wif = do+  guard (T.length wif <= maxWifLength)   bs <- decodeBase58Check wif+  (prefix, rest) <- BS.uncons bs   -- Check that this is a private key-  guard (BS.head bs == net.secretPrefix)-  case BS.length bs of+  guard (prefix == net.secretPrefix)+  case BS.length rest of     -- Uncompressed format-    33 -> wrapSecKey False <$> (secKey . BS.tail) bs-    -- Compressed format-    34 -> do-      guard $ BS.last bs == 0x01-      wrapSecKey True <$> (secKey . BS.tail . BS.init) bs+    32 -> wrapSecKey False <$> decodeSecKey rest+    -- Compressed format: the key is followed by a 0x01 flag byte+    33 -> do+      guard $ BS.last rest == 0x01+      wrapSecKey True <$> decodeSecKey (BS.init rest)     -- Bad length     _ -> Nothing 
src/Haskoin/Crypto/Keys/Extended.hs view
@@ -106,7 +106,7 @@ import Control.Applicative import Control.DeepSeq import Control.Exception (Exception, throw)-import Control.Monad (guard, mzero, unless, (<=<))+import Control.Monad (guard, mzero, unless, when, (<=<)) import Crypto.Secp256k1 import Data.Aeson as Aeson   ( FromJSON,@@ -122,6 +122,7 @@ import Data.Bits (clearBit, setBit, testBit) import Data.ByteString (ByteString) import Data.ByteString qualified as B+import Data.Char (isDigit) import Data.Bytes.Get   ( MonadGet       ( getByteString,@@ -162,6 +163,7 @@ import Text.Read as Read   ( Lexeme (Ident, Number, String),     Read (readPrec),+    ReadPrec,     lexP,     parens,     pfail,@@ -197,19 +199,33 @@     -- | private key of this node     key :: !SecKey   }-  deriving (Generic, Eq, Show, Read, NFData, Hashable)+  deriving (Generic, Eq, NFData, Hashable) +-- | The chain code and the key are not shown. Together they give every key+-- below this one, so a value that ends up in a log or an error message+-- must not reveal them. There is no 'Read' instance for the same reason:+-- use 'xPrvExport' and 'xPrvImport' to write a key out and read it back.+instance Show XPrvKey where+  showsPrec d k =+    showParen (d > 10) $+      showString "XPrvKey {depth = "+        . shows k.depth+        . showString ", parent = "+        . shows k.parent+        . showString ", index = "+        . shows k.index+        . showString ", chain = <redacted>, key = <redacted>}"+ instance Marshal Network XPrvKey where   marshalGet net = do     ver <- getWord32be     unless (ver == net.xPrvPrefix) $       fail "Get: Invalid version for extended private key"-    XPrvKey-      <$> getWord8-      <*> deserialize-      <*> getWord32be-      <*> deserialize-      <*> getPadPrvKey+    d <- getWord8+    p <- deserialize+    i <- getWord32be+    checkMasterFields d p i+    XPrvKey d p i <$> deserialize <*> getPadPrvKey    marshalPut net k = do     putWord32be net.xPrvPrefix@@ -250,11 +266,12 @@     ver <- getWord32be     unless (ver == net.xPubPrefix) $       fail "Get: Invalid version for extended public key"-    XPubKey-      <$> getWord8-      <*> deserialize-      <*> getWord32be-      <*> deserialize+    d <- getWord8+    p <- deserialize+    i <- getWord32be+    checkMasterFields d p i+    XPubKey d p i+      <$> deserialize       <*> ((\PublicKey {point} -> point) <$> marshalGet ctx)    marshalPut (net, ctx) k = do@@ -276,14 +293,24 @@    marshalEncoding (net, ctx) = text . xPubExport net ctx +-- | A key at depth 0 is a master key. BIP32 requires its parent fingerprint+-- and child index to be zero; test vector 5 lists keys that break this rule+-- as invalid.+checkMasterFields :: (MonadGet m) => Word8 -> Fingerprint -> KeyIndex -> m ()+checkMasterFields depth parent index =+  when (depth == 0 && (parent /= Fingerprint 0 || index /= 0)) $+    fail "Extended key at depth 0 must have a zero parent fingerprint and index"+ -- | Build a BIP32 compatible extended private key from a bytestring. This will--- produce a root node (@depth=0@ and @parent=0@).+-- produce a root node (@depth=0@ and @parent=0@). Throws 'DerivationException'+-- in the vanishingly unlikely case that the seed gives a key outside the curve+-- order, which BIP32 declares invalid. makeXPrvKey :: ByteString -> XPrvKey makeXPrvKey bs =   XPrvKey 0 (Fingerprint 0) 0 c k   where     (p, c) = split512 $ hmac512 "Bitcoin seed" bs-    k = fromMaybe err (secKey (runPutS (serialize p)))+    k = fromMaybe err (decodeSecKey (runPutS (serialize p)))     err = throw $ DerivationException "Invalid seed"  -- | Derive an extended public key from an extended private key. This function@@ -310,9 +337,9 @@   -- | extended child private key   XPrvKey prvSubKey ctx xkey child-  | child >= 0 && child < 0x80000000 =-      XPrvKey (xkey.depth + 1) (xPrvFP ctx xkey) child c k-  | otherwise = error "Invalid child derivation index"+  | child < 0x80000000 =+      XPrvKey (nextDepth xkey.depth) (xPrvFP ctx xkey) child c k+  | otherwise = throw hardIndexException   where     pK = (deriveXPubKey ctx xkey).key     m = B.append (exportPubKey ctx True pK) (runPutS (serialize child))@@ -331,9 +358,9 @@   -- | extended child public key   XPubKey pubSubKey ctx xKey child-  | child >= 0 && child < 0x80000000 =-      XPubKey (xKey.depth + 1) (xPubFP ctx xKey) child c pK-  | otherwise = error "Invalid child derivation index"+  | child < 0x80000000 =+      XPubKey (nextDepth xKey.depth) (xPubFP ctx xKey) child c pK+  | otherwise = throw hardIndexException   where     m = B.append (exportPubKey ctx True xKey.key) (runPutS $ serialize child)     (a, c) = split512 $ hmac512 (runPutS $ serialize xKey.chain) m@@ -355,9 +382,9 @@   -- | extended child private key   XPrvKey hardSubKey ctx xkey child-  | child >= 0 && child < 0x80000000 =-      XPrvKey (xkey.depth + 1) (xPrvFP ctx xkey) i c k-  | otherwise = error "Invalid child derivation index"+  | child < 0x80000000 =+      XPrvKey (nextDepth xkey.depth) (xPrvFP ctx xkey) i c k+  | otherwise = throw hardIndexException   where     i = setBit child 31     m = B.append (bsPadPrvKey xkey.key) (runPutS $ serialize i)@@ -365,6 +392,21 @@     k = fromMaybe err $ tweakSecKey ctx xkey.key a     err = throw $ DerivationException "Invalid hardSubKey derivation" +-- | The exception for a child index with the hard bit set. The derivation+-- functions take the index without the bit, below 2^31, and 'hardSubKey'+-- sets the bit itself. Every failure of a derivation throws+-- 'DerivationException', so that a caller can catch them all at once.+hardIndexException :: DerivationException+hardIndexException = DerivationException "Invalid child derivation index"++-- | Depth of a child key. BIP32 stores the depth in one byte, so a key at+-- depth 255 can have no children. Bitcoin Core wraps around to 0 here, which+-- gives a key that cannot be told from a master key. This library refuses, as+-- rust-bitcoin does.+nextDepth :: Word8 -> Word8+nextDepth 255 = throw $ DerivationException "Maximum derivation depth exceeded"+nextDepth d = d + 1+ -- | Returns true if the extended private key was derived through a hard -- derivation. xPrvIsHard :: XPrvKey -> Bool@@ -427,17 +469,22 @@ xPubAddr :: Ctx -> XPubKey -> Address xPubAddr ctx xkey = pubKeyAddr ctx (wrapPubKey True xkey.key) --- | Compute a SegWit P2WPKH address for an extended public key.+-- | Compute a SegWit P2WPKH address for an extended public key. Extended+-- public keys are always compressed, so the address always exists. xPubWitnessAddr :: Ctx -> XPubKey -> Address-xPubWitnessAddr ctx xkey =-  pubKeyWitnessAddr ctx (wrapPubKey True xkey.key)+xPubWitnessAddr ctx xkey = p2wpkhAddr (xPubKeyHash ctx xkey)  -- | Compute a backwards-compatible SegWit P2SH-P2WPKH address for an extended--- public key.+-- public key. Extended public keys are always compressed, so the address+-- always exists. xPubCompatWitnessAddr :: Ctx -> XPubKey -> Address xPubCompatWitnessAddr ctx xkey =-  pubKeyCompatWitnessAddr ctx (wrapPubKey True xkey.key)+  payToScriptAddress ctx (PayWitnessPKHash (xPubKeyHash ctx xkey)) +-- | HASH160 of the compressed public key of an extended public key.+xPubKeyHash :: Ctx -> XPubKey -> Hash160+xPubKeyHash ctx xkey = addressHash (marshal ctx (wrapPubKey True xkey.key))+ -- | Exports an extended private key to the BIP32 key export format ('Base58'). xPrvExport :: Network -> XPrvKey -> Base58 xPrvExport net = encodeBase58Check . marshal net@@ -447,17 +494,40 @@ xPubExport net ctx = encodeBase58Check . marshal (net, ctx)  -- | Decodes a BIP32 encoded extended private key. This function will fail if--- invalid base 58 characters are detected or if the checksum fails.+-- invalid base 58 characters are detected, if the checksum fails, if the+-- payload is not exactly 78 bytes, or if the key fails the checks of BIP32. xPrvImport :: Network -> Base58 -> Maybe XPrvKey xPrvImport net =-  eitherToMaybe . unmarshal net <=< decodeBase58Check+  eitherToMaybe . unmarshal net <=< decodeExtendedKey  -- | Decodes a BIP32 encoded extended public key. This function will fail if--- invalid base 58 characters are detected or if the checksum fails.+-- invalid base 58 characters are detected, if the checksum fails, if the+-- payload is not exactly 78 bytes, or if the key fails the checks of BIP32. xPubImport :: Network -> Ctx -> Base58 -> Maybe XPubKey xPubImport net ctx =-  eitherToMaybe . unmarshal (net, ctx) <=< decodeBase58Check+  eitherToMaybe . unmarshal (net, ctx) <=< decodeExtendedKey +-- | Size in bytes of a serialized extended key, without its checksum.+extendedKeySize :: Int+extendedKeySize = 78++-- | Longest Base58 string an extended key can take. The 82 bytes of key and+-- checksum never need more than 112 digits because 58^112 is larger than+-- 256^82. Longer strings are rejected before decoding, since the time to decode+-- Base58 grows with the square of the length.+maxExtendedKeyLength :: Int+maxExtendedKeyLength = 112++-- | Decode the Base58Check form of an extended key into its 78 bytes. A payload+-- of another size is refused, as Bitcoin Core does, so that trailing bytes or+-- an uncompressed public key cannot slip through.+decodeExtendedKey :: Base58 -> Maybe ByteString+decodeExtendedKey t = do+  guard (Text.length t <= maxExtendedKeyLength)+  bs <- decodeBase58Check t+  guard (B.length bs == extendedKeySize)+  return bs+ -- | Export an extended private key to WIF (Wallet Import Format). xPrvWif :: Network -> XPrvKey -> Base58 xPrvWif net xkey = toWif net (wrapSecKey True xkey.key)@@ -552,12 +622,13 @@       let (a, rdm) = deriveMSAddr ctx keys m i        in (a, rdm, i) --- | Helper function to go through derivation indices.+-- | Helper function to go through derivation indices. Throws+-- 'DerivationException' for an index with the hard bit set. cycleIndex :: KeyIndex -> [KeyIndex] cycleIndex i   | i == 0 = cycle [0 .. 0x7fffffff]   | i < 0x80000000 = cycle $ [i .. 0x7fffffff] ++ [0 .. (i - 1)]-  | otherwise = error $ "cycleIndex: invalid index " ++ show i+  | otherwise = throw $ DerivationException $ "cycleIndex: invalid index " ++ show i  {- Derivation Paths -} @@ -631,7 +702,8 @@     if nextA == nextB then iA `compare` iB else nextA `compare` nextB   (nextA :/ iA) `compare` (nextB :/ iB) =     if nextA == nextB then iA `compare` iB else nextA `compare` nextB-  -- Different hardness: hard paths are LT soft paths+  -- Different hardness: a soft path sorts before a hard one, as it does when+  -- comparing the index lists of 'pathToList', where the hard bit is set.   (nextA :/ _iA) `compare` (nextB :| _iB) =     if nextA == nextB then LT else nextA `compare` nextB   (nextA :| _iA) `compare` (nextB :/ _iB) =@@ -904,12 +976,24 @@ appendBip32Segment d (Bip32SoftIndex i) = d :/ i appendBip32Segment d (Bip32HardIndex i) = d :| i --- | Parse a BIP32 derivation path index element from a string.+-- | Parse a BIP32 derivation path index element from a string. The element is+-- a decimal number below 2^31, followed by @'@, @h@ or @H@ for a hardened+-- index. Bitcoin Core accepts the same three markers. The number is read as an+-- 'Integer' so that a value too large for 32 bits cannot wrap around. parseBip32PathIndex :: String -> Maybe Bip32PathIndex-parseBip32PathIndex segment = case reads segment of-  [(i, "")] -> guard (is31Bit i) >> return (Bip32SoftIndex i)-  [(i, "'")] -> guard (is31Bit i) >> return (Bip32HardIndex i)-  _ -> Nothing+parseBip32PathIndex segment =+  case span isDigit segment of+    ("", _) -> Nothing+    (digits, "") -> Bip32SoftIndex <$> parseIndex digits+    (digits, [marker])+      | marker `elem` hardMarkers -> Bip32HardIndex <$> parseIndex digits+    _ -> Nothing+  where+    hardMarkers = "'hH" :: String+    parseIndex digits = do+      let n = read digits :: Integer+      guard (is31Bit n)+      return (fromInteger n)  -- | Type for BIP32 path index element. data Bip32PathIndex@@ -932,19 +1016,20 @@         parens $ do           Ident "Bip32HardIndex" <- lexP           Number n <- lexP-          maybe-            pfail-            (return . Bip32HardIndex . fromIntegral)-            (numberToInteger n)+          maybe pfail (fmap Bip32HardIndex . readIndex) (numberToInteger n)       s =         parens $ do           Ident "Bip32SoftIndex" <- lexP           Number n <- lexP-          maybe-            pfail-            (return . Bip32SoftIndex . fromIntegral)-            (numberToInteger n)+          maybe pfail (fmap Bip32SoftIndex . readIndex) (numberToInteger n) +-- | Read a derivation index that was lexed as a number, refusing values that+-- do not fit in 31 bits instead of wrapping them around.+readIndex :: Integer -> ReadPrec KeyIndex+readIndex i+  | is31Bit i = return (fromInteger i)+  | otherwise = pfail+ -- | Test whether the number could be a valid BIP32 derivation index. is31Bit :: (Integral a) => a -> Bool is31Bit i = i >= 0 && i < 0x80000000@@ -967,7 +1052,7 @@       { xpub :: !XPubKey,         net :: !Network       }-  deriving (Show, Read, Eq, Generic, NFData)+  deriving (Show, Eq, Generic, NFData)  -- | Apply a parsed path to an extended key to derive the new key defined in the -- path. If the path starts with /m/, a private key will be returned and if the@@ -1045,8 +1130,8 @@   pad <- getWord8   unless (pad == 0x00) $ fail "Private key must be padded with 0x00"   bs <- getByteString 32-  case secKey bs of-    Nothing -> fail $ "Could not decode secret key: " ++ cs (encodeHex bs)+  case decodeSecKey bs of+    Nothing -> fail "Secret key is zero or not below the curve order"     Just x -> return x  -- | Serialize HDW-specific private key.
src/Haskoin/Crypto/Keys/Extended/Internal.hs view
@@ -12,7 +12,7 @@ where  import Control.DeepSeq (NFData)-import Control.Monad ((>=>))+import Control.Monad (unless) import Data.Aeson   ( FromJSON (parseJSON),     ToJSON (toEncoding, toJSON),@@ -22,10 +22,9 @@ import Data.Binary (Binary (..)) import Data.Bytes.Get (getWord32be) import Data.Bytes.Put (putWord32be)+import Data.ByteString qualified as B import Data.Bytes.Serial (Serial (..))-import Data.Either (fromRight) import Data.Hashable (Hashable)-import Data.Maybe (fromMaybe) import Data.Serialize (Serialize (..)) import Data.Serialize qualified as S import Data.String (IsString (..))@@ -44,29 +43,24 @@ fingerprintToText :: Fingerprint -> Text fingerprintToText = encodeHex . S.encode +-- | Read a fingerprint from exactly eight hex digits. textToFingerprint :: Text -> Either String Fingerprint-textToFingerprint =-  maybe (Left "Fingerprint: invalid hex") Right . decodeHex >=> S.decode+textToFingerprint t = do+  bs <- maybe (Left "Fingerprint: invalid hex") Right (decodeHex t)+  unless (B.length bs == 4) $ Left "Fingerprint: expected 4 bytes"+  S.decode bs  instance Show Fingerprint where   show = show . Text.unpack . encodeHex . S.encode  instance Read Fingerprint where-  readPrec =-    readPrec-      >>= maybe (fail "Fingerprint: invalid hex") pure . decodeHex-      >>= either (fail . ("Fingerprint: " <>)) pure . S.decode+  readPrec = readPrec >>= either fail pure . textToFingerprint  instance IsString Fingerprint where   fromString =-    fromRight decodeError-      . S.decode-      . fromMaybe hexError-      . decodeHex+    either (error . ("Fingerprint literal: " <>)) id+      . textToFingerprint       . Text.pack-    where-      decodeError = error "Fingerprint literal: Unable to decode"-      hexError = error "Fingerprint literal: Invalid hex"  instance Serial Fingerprint where   serialize = putWord32be . (.get)
src/Haskoin/Crypto/Keys/Mnemonic.hs view
@@ -25,7 +25,7 @@   ) where -import Control.Monad (when)+import Control.Monad (unless, when) import Crypto.Hash (SHA256 (..), hashWith) import Crypto.KDF.PBKDF2 (Parameters (..), fastPBKDF2_SHA512) import Data.Bits (shiftL, shiftR)@@ -39,6 +39,7 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as E+import Data.Text.Normalize (NormalizationMode (NFKD), normalize) import Data.Vector (Vector, (!)) import Data.Vector qualified as V import Haskoin.Util@@ -63,16 +64,28 @@ pbkdfParams :: Parameters pbkdfParams = Parameters {iterCounts = 2048, outputLength = 64} --- | Provide intial 'Entropy' as a 'ByteString' of length multiple of 4 bytes.--- Output a 'Mnemonic' sentence.+-- | Smallest entropy BIP39 allows: 128 bits, which gives 12 words.+minEntropyBytes :: Int+minEntropyBytes = 16++-- | Largest entropy BIP39 allows: 256 bits, which gives 24 words.+maxEntropyBytes :: Int+maxEntropyBytes = 32++-- | Word counts of the sentences BIP39 allows, for 128 to 256 bits of entropy.+validWordCounts :: [Int]+validWordCounts = [12, 15, 18, 21, 24]++-- | Provide initial 'Entropy' as a 'ByteString' of 16 to 32 bytes, in steps+-- of 4 bytes, as BIP39 allows. Output a 'Mnemonic' sentence of 12 to 24 words. toMnemonic :: Entropy -> Either String Mnemonic toMnemonic ent = do-  when (B.null ent) $-    Left "toMnemonic: entropy can not be empty"   when (remainder /= 0) $     Left "toMnemonic: entropy must be multiples of 4 bytes"-  when (cs_len > 16) $-    Left "toMnemonic: maximum entropy is 64 bytes (512 bits)"+  when (B.length ent < minEntropyBytes) $+    Left "toMnemonic: minimum entropy is 16 bytes (128 bits)"+  when (B.length ent > maxEntropyBytes) $+    Left "toMnemonic: maximum entropy is 32 bytes (256 bits)"   return ms   where     (cs_len, remainder) = B.length ent `quotRem` 4@@ -83,16 +96,16 @@ -- | Revert 'toMnemonic'. Do not use this to generate a 'Seed'. Instead use -- 'mnemonicToSeed'. This outputs the original 'Entropy' used to generate a -- 'Mnemonic' sentence.+--+-- The sentence is NFKD-normalised before its words are looked up, as BIP39+-- requires. Only sentences of 12, 15, 18, 21 or 24 words are accepted. fromMnemonic :: Mnemonic -> Either String Entropy fromMnemonic ms = do   when (T.null ms) $     Left "fromMnemonic: empty mnemonic"-  when (word_count > 48) $-    Left $-      "fromMnemonic: too many words: " ++ show word_count-  when (word_count `mod` 3 /= 0) $+  unless (word_count `elem` validWordCounts) $     Left $-      "fromMnemonic: wrong number of words:" ++ show word_count+      "fromMnemonic: wrong number of words: " ++ show word_count   ms_bs <- indicesToBS =<< getIndices ms_words   let (ms_ent, ms_cs) = B.splitAt (ent_len * 4) ms_bs       ms_cs_num = numCS cs_len ms_cs@@ -102,7 +115,7 @@       "fromMnemonic: checksum failed: " ++ sh ent_cs_num ms_cs_num   return ms_ent   where-    ms_words = T.words ms+    ms_words = T.words (normalize NFKD ms)     word_count = length ms_words     (ent_len, cs_len) = (word_count * 11) `quotRem` 32     sh cs_a cs_b = show cs_a ++ " /= " ++ show cs_b@@ -120,18 +133,20 @@       x -> flip shiftR x  -- | Turn an arbitrary sequence of characters into a 512-bit 'Seed'. Use--- 'mnemonicToSeed' to get a seed from a 'Mnemonic' sentence. Warning: Does not--- perform NFKD normalization.+-- 'mnemonicToSeed' to get a seed from a 'Mnemonic' sentence. Both inputs are+-- NFKD-normalised first, as BIP39 requires. anyToSeed :: Passphrase -> Mnemonic -> Seed anyToSeed pf ms =   fastPBKDF2_SHA512     pbkdfParams-    (E.encodeUtf8 ms)-    ("mnemonic" `mappend` E.encodeUtf8 pf)+    (E.encodeUtf8 (normalize NFKD ms))+    ("mnemonic" `mappend` E.encodeUtf8 (normalize NFKD pf))  -- | Get a 512-bit 'Seed' from a 'Mnemonic' sentence. Will validate checksum. -- 'Passphrase' can be used to protect the 'Mnemonic'. Use an empty string as--- 'Passphrase' if none is required.+-- 'Passphrase' if none is required. The sentence and the passphrase are+-- NFKD-normalised as BIP39 requires, so a passphrase with accented letters+-- gives the same seed here as in other wallets. mnemonicToSeed :: Passphrase -> Mnemonic -> Either String Seed mnemonicToSeed pf ms = do   ent <- fromMnemonic ms@@ -142,7 +157,7 @@ getIndices :: [Text] -> Either String [Int] getIndices ws   | null n = return $ catMaybes i-  | otherwise = Left $ "getIndices: words not found: " ++ cs w+  | otherwise = Left $ "fromMnemonic: words not in the list: " ++ cs w   where     i = map (`M.lookup` wordListMap) ws     n = elemIndices Nothing i
src/Haskoin/Crypto/Signature.hs view
@@ -17,6 +17,7 @@ module Haskoin.Crypto.Signature   ( -- * Signatures     signHash,+    hashToMsg,     verifyHashSig,     isCanonicalHalfOrder,     decodeStrictSig,@@ -35,7 +36,7 @@ import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial-import Data.Maybe (fromMaybe, isNothing)+import Data.Maybe (fromMaybe) import Data.Serialize (Serialize (..)) import Data.Text qualified as T import Haskoin.Crypto.Hash@@ -56,9 +57,7 @@  -- | Verify an ECDSA signature for a 256-bit hash. verifyHashSig :: Ctx -> Hash256 -> Sig -> PubKey -> Bool-verifyHashSig ctx h s p = verifySig ctx p norm (hashToMsg h)-  where-    norm = fromMaybe s (normalizeSig ctx s)+verifyHashSig ctx h s p = verifySig ctx p (normalizeSig ctx s) (hashToMsg h)  instance Marshal Ctx Sig where   marshalGet ctx = do@@ -82,15 +81,17 @@ instance MarshalJSON Ctx Sig where   marshalValue ctx = String . encodeHex . exportSig ctx   marshalEncoding ctx = hexEncoding . L.fromStrict . exportSig ctx+  -- Decoding is as strict as the binary form: low-S and non-zero components.   unmarshalValue ctx =     withText "Sig" $ \t ->-      case decodeHex t >>= importSig ctx of+      case decodeHex t >>= decodeStrictSig ctx of         Nothing -> fail $ "Could not decode signature: " <> T.unpack t         Just s -> return s --- | Is canonical half order.+-- | Is canonical half order: the s component is not above half the curve+-- order, the only form 'verifySig' accepts. isCanonicalHalfOrder :: Ctx -> Sig -> Bool-isCanonicalHalfOrder ctx = isNothing . normalizeSig ctx+isCanonicalHalfOrder = isLowS  -- | Decode signature strictly. decodeStrictSig :: Ctx -> ByteString -> Maybe Sig
src/Haskoin/Network.hs view
@@ -17,11 +17,15 @@     module Common,     module Message,     module Bloom,+    module CompactBlock,+    module BlockFilter,   ) where +import Haskoin.Network.BlockFilter as BlockFilter import Haskoin.Network.Bloom as Bloom import Haskoin.Network.Common as Common+import Haskoin.Network.CompactBlock as CompactBlock import Haskoin.Network.Constants as Constants import Haskoin.Network.Data as Data import Haskoin.Network.Message as Message
+ src/Haskoin/Network/BlockFilter.hs view
@@ -0,0 +1,215 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE NoFieldSelectors #-}++-- |+-- Module      : Haskoin.Network.BlockFilter+-- Copyright   : No rights reserved+-- License     : MIT+-- Maintainer  : jprupp@protonmail.ch+-- Stability   : experimental+-- Portability : POSIX+--+-- Messages of client-side block filtering (BIP 157): @getcfilters@,+-- @cfilter@, @getcfheaders@, @cfheaders@, @getcfcheckpt@ and @cfcheckpt@.+-- The filters themselves are defined by BIP 158.+module Haskoin.Network.BlockFilter+  ( FilterType,+    basicFilterType,+    GetCFilters (..),+    CFilter (..),+    GetCFHeaders (..),+    CFHeaders (..),+    GetCFCheckpt (..),+    CFCheckpt (..),+  )+where++import Control.DeepSeq+import Data.Binary (Binary (..))+import Data.ByteString (ByteString)+import Data.Bytes.Get+import Data.Bytes.Put+import Data.Bytes.Serial+import Data.Serialize (Serialize (..))+import Data.Word (Word32, Word8)+import GHC.Generics (Generic)+import Haskoin.Block.Common (BlockHash)+import Haskoin.Crypto.Hash (Hash256)+import Haskoin.Network.Common++-- | Kind of filter, as defined by BIP 158.+type FilterType = Word8++-- | The basic filter type of BIP 158, the only one defined so far.+basicFilterType :: FilterType+basicFilterType = 0++-- | The @getcfilters@ message. It asks for the filters of a range of blocks.+-- BIP 157 limits a range to 1000 blocks.+data GetCFilters = GetCFilters+  { -- | kind of filter+    filterType :: !FilterType,+    -- | height of the first block of the range+    startHeight :: !Word32,+    -- | hash of the last block of the range+    stopHash :: !BlockHash+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial GetCFilters where+  deserialize = GetCFilters <$> getWord8 <*> getWord32le <*> deserialize+  serialize g = do+    putWord8 g.filterType+    putWord32le g.startHeight+    serialize g.stopHash++instance Binary GetCFilters where+  put = serialize+  get = deserialize++instance Serialize GetCFilters where+  put = serialize+  get = deserialize++-- | The @cfilter@ message: the filter of one block.+data CFilter = CFilter+  { -- | kind of filter+    filterType :: !FilterType,+    -- | hash of the block the filter is for+    blockHash :: !BlockHash,+    -- | the serialized filter+    filterBytes :: !ByteString+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial CFilter where+  deserialize = CFilter <$> getWord8 <*> deserialize <*> getVarIntBytes+  serialize c = do+    putWord8 c.filterType+    serialize c.blockHash+    putVarIntBytes c.filterBytes++instance Binary CFilter where+  put = serialize+  get = deserialize++instance Serialize CFilter where+  put = serialize+  get = deserialize++-- | The @getcfheaders@ message. It asks for the filter hashes of a range of+-- blocks, with the filter header before the range. BIP 157 limits a range to+-- 2000 blocks.+data GetCFHeaders = GetCFHeaders+  { -- | kind of filter+    filterType :: !FilterType,+    -- | height of the first block of the range+    startHeight :: !Word32,+    -- | hash of the last block of the range+    stopHash :: !BlockHash+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial GetCFHeaders where+  deserialize = GetCFHeaders <$> getWord8 <*> getWord32le <*> deserialize+  serialize g = do+    putWord8 g.filterType+    putWord32le g.startHeight+    serialize g.stopHash++instance Binary GetCFHeaders where+  put = serialize+  get = deserialize++instance Serialize GetCFHeaders where+  put = serialize+  get = deserialize++-- | The @cfheaders@ message, the answer to 'GetCFHeaders'. The filter header+-- of each block is the double SHA-256 of its filter hash and the previous+-- filter header.+data CFHeaders = CFHeaders+  { -- | kind of filter+    filterType :: !FilterType,+    -- | hash of the last block of the range+    stopHash :: !BlockHash,+    -- | filter header of the block before the range+    previousHeader :: !Hash256,+    -- | filter hash of every block of the range+    filterHashes :: ![Hash256]+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial CFHeaders where+  deserialize =+    CFHeaders+      <$> getWord8+      <*> deserialize+      <*> deserialize+      <*> getVarIntList deserialize+  serialize c = do+    putWord8 c.filterType+    serialize c.stopHash+    serialize c.previousHeader+    putVarIntList serialize c.filterHashes++instance Binary CFHeaders where+  put = serialize+  get = deserialize++instance Serialize CFHeaders where+  put = serialize+  get = deserialize++-- | The @getcfcheckpt@ message. It asks for the filter headers at every+-- thousandth block up to the given one.+data GetCFCheckpt = GetCFCheckpt+  { -- | kind of filter+    filterType :: !FilterType,+    -- | hash of the last block of the chain to cover+    stopHash :: !BlockHash+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial GetCFCheckpt where+  deserialize = GetCFCheckpt <$> getWord8 <*> deserialize+  serialize g = do+    putWord8 g.filterType+    serialize g.stopHash++instance Binary GetCFCheckpt where+  put = serialize+  get = deserialize++instance Serialize GetCFCheckpt where+  put = serialize+  get = deserialize++-- | The @cfcheckpt@ message, the answer to 'GetCFCheckpt'.+data CFCheckpt = CFCheckpt+  { -- | kind of filter+    filterType :: !FilterType,+    -- | hash of the last block of the chain covered+    stopHash :: !BlockHash,+    -- | filter headers at heights 1000, 2000, and so on+    filterHeaders :: ![Hash256]+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial CFCheckpt where+  deserialize = CFCheckpt <$> getWord8 <*> deserialize <*> getVarIntList deserialize+  serialize c = do+    putWord8 c.filterType+    serialize c.stopHash+    putVarIntList serialize c.filterHeaders++instance Binary CFCheckpt where+  put = serialize+  get = deserialize++instance Serialize CFCheckpt where+  put = serialize+  get = deserialize
src/Haskoin/Network/Bloom.hs view
@@ -26,6 +26,9 @@     BloomFilter (..),     FilterLoad (..),     FilterAdd (..),+    maxBloomSize,+    maxHashFuncs,+    maxFilterAddSize,     bloomCreate,     bloomInsert,     bloomContains,@@ -34,11 +37,13 @@     isBloomFull,     acceptsFilters,     bloomRelevantUpdate,+    scriptPushData,   ) where +import Control.Applicative ((<|>)) import Control.DeepSeq-import Control.Monad (forM_, replicateM)+import Control.Monad (when) import Crypto.Secp256k1 (Ctx) import Data.Binary (Binary (..)) import Data.Bits@@ -52,20 +57,29 @@ import Data.List (foldl') import Data.Sequence qualified as S import Data.Serialize (Serialize (..))+import Data.Serialize qualified as Ser import Data.Word import GHC.Generics (Generic) import Haskoin.Network.Common+import Haskoin.Script.Common import Haskoin.Script.Standard import Haskoin.Transaction.Common import Haskoin.Util.Marshal --- | 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001%+-- | Largest filter a peer may load, in bytes. 20,000 items with fp rate+-- < 0.1% or 10,000 items and <0.0001%. maxBloomSize :: Int maxBloomSize = 36000 +-- | Largest number of hash functions a filter may use. maxHashFuncs :: Word32 maxHashFuncs = 50 +-- | Largest data element a 'FilterAdd' message may carry, in bytes. It is+-- the largest element a script can push (@MAX_SCRIPT_ELEMENT_SIZE@).+maxFilterAddSize :: Int+maxFilterAddSize = 520+ ln2Squared :: Double ln2Squared = 0.4804530139182014246671025263266649717305529515945455 @@ -86,13 +100,14 @@     BloomUpdateP2PubKeyOnly   deriving (Eq, Show, Read, Generic, NFData) +-- | Only the two low bits of the flags byte count, as with the+-- @BLOOM_UPDATE_MASK@ of Bitcoin Core. The value 3 means no update. instance Serial BloomFlags where-  deserialize = go =<< getWord8+  deserialize = go . (.&. 3) =<< getWord8     where-      go 0 = return BloomUpdateNone       go 1 = return BloomUpdateAll       go 2 = return BloomUpdateP2PubKeyOnly-      go _ = fail "BloomFlags get: Invalid bloom flag"+      go _ = return BloomUpdateNone    serialize f = putWord8 $ case f of     BloomUpdateNone -> 0@@ -125,15 +140,21 @@   }   deriving (Eq, Show, Read, Generic, NFData) +-- | A filter with more than 'maxBloomSize' bytes or more than 'maxHashFuncs'+-- hash functions fails the parse, as Bitcoin Core refuses such a+-- @filterload@ message. instance Serial BloomFilter where-  deserialize =-    BloomFilter-      <$> (S.fromList <$> (readDat =<< deserialize))-      <*> getWord32le-      <*> getWord32le-      <*> deserialize-    where-      readDat (VarInt len) = replicateM (fromIntegral len) getWord8+  deserialize = do+    VarInt len <- deserialize+    when (len > fromIntegral maxBloomSize) $+      fail ("Bloom filter of " ++ show len ++ " bytes is too large")+    bytes <- getByteString (fromIntegral len)+    functions <- getWord32le+    when (functions > maxHashFuncs) $+      fail ("Bloom filter with " ++ show functions ++ " hash functions has too many")+    tweak <- getWord32le+    flags <- deserialize+    return BloomFilter {array = S.fromList (BS.unpack bytes), ..}    serialize BloomFilter {..} = do     putVarInt $ S.length array@@ -173,15 +194,16 @@   deriving (Eq, Show, Read, Generic)   deriving newtype (NFData) +-- | An element longer than 'maxFilterAddSize' fails the parse, as Bitcoin+-- Core refuses such a @filteradd@ message. instance Serial FilterAdd where   deserialize = do-    (VarInt len) <- deserialize-    dat <- getByteString $ fromIntegral len+    dat <- getVarIntBytes+    when (BS.length dat > maxFilterAddSize) $+      fail ("filteradd element of " ++ show (BS.length dat) ++ " bytes is too long")     return $ FilterAdd dat -  serialize (FilterAdd bs) = do-    putVarInt $ BS.length bs-    putByteString bs+  serialize (FilterAdd bs) = putVarIntBytes bs  instance Binary FilterAdd where   put = serialize@@ -192,7 +214,10 @@   get = deserialize  -- | Build a bloom filter that will provide the given false positive rate when--- the given number of elements have been inserted.+-- the given number of elements have been inserted. The size is capped at+-- 'maxBloomSize' and the number of hash functions at 'maxHashFuncs'. A filter+-- built for many more elements than fit in 'maxBloomSize' gets zero hash+-- functions and then matches everything, as in Bitcoin Core. bloomCreate ::   -- | number of elements   Int ->@@ -213,7 +238,9 @@     a = -1 / ln2Squared * fromIntegral numElem * log fpRate     -- Maximum size in bits     b = fromIntegral $ maxBloomSize * 8-    numHashF = truncate $ min c (fromIntegral maxHashFuncs)+    numHashF+      | numElem == 0 = 0+      | otherwise = truncate $ min c (fromIntegral maxHashFuncs)     -- Suggested number of hash functions     c = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2 @@ -223,8 +250,14 @@   where     seed = hashNum * 0xfba4c795 + b.tweak +-- | Bit positions that the hash functions of a filter map some data to. There+-- are as many as the filter has hash functions. The filter must not be empty.+bloomBits :: BloomFilter -> ByteString -> [Word32]+bloomBits b bs =+  map (\i -> bloomHash b i bs) (take (fromIntegral b.functions) [0 ..])+ -- | Insert arbitrary data into a bloom filter. Returns the new bloom filter--- containing the new data.+-- containing the new data. An empty filter stays empty. bloomInsert ::   -- | Original bloom filter   BloomFilter ->@@ -233,19 +266,18 @@   -- | Bloom filter containing the new data   BloomFilter bloomInsert b bs-  | isBloomFull b = b-  | otherwise = b {array = dat}+  | S.null b.array = b+  | otherwise = b {array = foldl' setBit' b.array (bloomBits b bs)}   where-    idxs = map (\i -> bloomHash b i bs) [0 .. b.functions - 1]-    upd s i =+    setBit' s i =       S.adjust         (.|. bitMask !! fromIntegral (7 .&. i))         (fromIntegral $ i `shiftR` 3)         s-    dat = foldl upd b.array idxs  -- | Tests if some arbitrary data matches the filter. This can be either because--- the data was inserted into the filter or because it is a false positive.+-- the data was inserted into the filter or because it is a false positive. An+-- empty filter, and a filter with no hash functions, match everything. bloomContains ::   -- | Bloom filter   BloomFilter ->@@ -254,20 +286,43 @@   -- | Returns True if the data matches the filter   Bool bloomContains b bs-  | isBloomFull b = True-  | isBloomEmpty b = False-  | otherwise = all isSet idxs+  | S.null b.array = True+  | otherwise = all isSet (bloomBits b bs)   where-    s = b.array-    idxs = map (\i -> bloomHash b i bs) [0 .. b.functions - 1]     isSet i =-      S.index s (fromIntegral $ i `shiftR` 3)+      S.index b.array (fromIntegral $ i `shiftR` 3)         .&. (bitMask !! fromIntegral (7 .&. i))         /= 0 --- | Checks if any of the outputs of a tx is in the current bloom filter.--- If it is, add the txid and vout as an outpoint (i.e. so that--- a future tx that spends the output won't be missed).+-- | Data elements pushed by a script, in order, without the empty ones. The+-- scan stops at the first byte sequence that is not a valid operation, as+-- @GetOp@ does in Bitcoin Core, and keeps the elements found before it.+scriptPushData :: ByteString -> [ByteString]+scriptPushData = either (const []) id . runGetS pushes+  where+    pushes :: Ser.Get [ByteString]+    pushes = do+      done <- isEmpty+      if done+        then return []+        else do+          op <- (Just <$> deserialize) <|> return Nothing+          case op of+            Nothing -> return []+            Just (OP_PUSHDATA d _)+              | not (BS.null d) -> (d :) <$> pushes+            Just _ -> pushes++-- | Checks if any output of a transaction matches the filter, as+-- @IsRelevantAndUpdate@ does in Bitcoin Core. An output matches when any data+-- element pushed by its script is in the filter. The outpoint of a matching+-- output is inserted according to the flags: always with 'BloomUpdateAll',+-- only for pay-to-pubkey and pay-to-multisig outputs with+-- 'BloomUpdateP2PubKeyOnly', and never with 'BloomUpdateNone'. The result is+-- 'Nothing' when no output matches, and otherwise the filter with the+-- outpoints added, which lets a future transaction spending them match too.+-- Non-standard output scripts are scanned like any other. An empty filter+-- matches every transaction and gets no outpoints. bloomRelevantUpdate ::   Ctx ->   -- | Bloom filter@@ -277,51 +332,28 @@   -- | Returns an updated bloom filter adding relevant output   Maybe BloomFilter bloomRelevantUpdate ctx b tx-  | isBloomFull b || isBloomEmpty b = Nothing-  | b.flags == BloomUpdateNone = Nothing-  | not (null matchOuts) = Just $ foldl' addRelevant b matchOuts+  | S.null b.array = Just b+  | found = Just updated   | otherwise = Nothing   where-    -- TxHash if we end up inserting an outpoint--    h = txHash tx-    -- Decode the scriptOutpus and add vOuts in case we make them outpoints-    decodedOutputScripts = traverse (unmarshal ctx . (.script)) tx.outputs-    err = error "Error Decoding output script"-    idxOutputScripts = either (const err) (zip [0 ..]) decodedOutputScripts-    -- Check if any txOuts were contained in the bloom filter-    matchFilter =-      filter (\(_, op) -> any (bloomContains b) (encodeScriptOut op))-    matchOuts = matchFilter idxOutputScripts-    addRelevant :: BloomFilter -> (Word32, ScriptOutput) -> BloomFilter-    addRelevant bf (id', scriptOut) =-      case (b.flags, scriptType) of-        -- We filtered out BloomUpdateNone so we insert any PayPk or PayMulSig--        (_, True) -> bloomInsert bf outpoint-        (BloomUpdateAll, _) -> bloomInsert bf outpoint-        _ -> error "Error Updating Bloom Filter with relevant outpoint"-      where-        outpoint = runPutS $ serialize $ OutPoint {hash = h, index = id'}-        scriptType = (\s -> isPayPK s || isPayMulSig s) scriptOut-    -- Encodes a scriptOutput so it can be checked agains the Bloom Filter-    encodeScriptOut :: ScriptOutput -> [ByteString]-    encodeScriptOut (PayPK pk) =-      return $ marshal ctx pk-    encodeScriptOut (PayPKHash ph) =-      return . runPutS $ serialize ph-    encodeScriptOut (PayMulSig outputMuSig _) =-      map (marshal ctx) outputMuSig-    encodeScriptOut (PayScriptHash sh) =-      return . runPutS $ serialize sh-    encodeScriptOut (PayWitnessPKHash ph) =-      return . runPutS $ serialize ph-    encodeScriptOut (PayWitnessScriptHash sh) =-      return . runPutS $ serialize sh-    encodeScriptOut (PayWitness _ wd) =-      return wd-    encodeScriptOut (DataCarrier dat) =-      return dat+    (found, updated) = foldl' step (False, b) (zip [0 ..] tx.outputs)+    step :: (Bool, BloomFilter) -> (Word32, TxOut) -> (Bool, BloomFilter)+    step (f, cur) (i, out)+      | any (bloomContains cur) (scriptPushData out.script) =+          (True, addOutpoint cur i out)+      | otherwise = (f, cur)+    addOutpoint :: BloomFilter -> Word32 -> TxOut -> BloomFilter+    addOutpoint cur i out =+      case b.flags of+        BloomUpdateAll -> bloomInsert cur (outpoint i)+        BloomUpdateP2PubKeyOnly+          | paysToKeys out.script -> bloomInsert cur (outpoint i)+        _ -> cur+    outpoint i = runPutS $ serialize $ OutPoint {hash = txHash tx, index = i}+    paysToKeys script =+      case unmarshal ctx script of+        Right o -> isPayPK o || isPayMulSig o+        Left _ -> False  -- | Returns True if the filter is empty (all bytes set to 0x00) isBloomEmpty :: BloomFilter -> Bool@@ -342,4 +374,4 @@  -- | Does the peer with these version services accept bloom filters? acceptsFilters :: Word64 -> Bool-acceptsFilters srv = srv .&. (1 `shiftL` 2) /= 0+acceptsFilters srv = srv .&. nodeBloom /= 0
src/Haskoin/Network/Common.hs view
@@ -4,6 +4,8 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoFieldSelectors #-} @@ -20,7 +22,16 @@   ( -- * Network Data Types     Addr (..),     NetworkAddressTime,+    AddrV2 (..),+    NetworkAddressV2 (..),+    AddressNetwork (..),+    addressNetworkId,+    addressNetworkLength,+    maxAddrV2Size,+    toNetworkAddressV2,+    fromNetworkAddressV2,     Alert (..),+    FeeFilter (..),     GetData (..),     Inv (..),     InvVector (..),@@ -34,9 +45,11 @@     Pong (..),     Reject (..),     RejectCode (..),+    SendTxRcncl (..),     VarInt (..),     VarString (..),     Version (..),+    maxUserAgentLength,     MessageCommand (..),     reject,     nodeNone,@@ -45,26 +58,33 @@     nodeBloom,     nodeWitness,     nodeXThin,+    nodeCompactFilters,+    nodeNetworkLimited,+    nodeP2PV2,     commandToString,     stringToCommand,     putVarInt,+    getVarIntList,+    putVarIntList,+    getVarIntBytes,+    putVarIntBytes,+    maxListSize,   ) where  import Control.DeepSeq-import Control.Monad (forM_, liftM2, replicateM, unless)+import Control.Monad (forM_, liftM2, replicateM, unless, when) import Data.Binary (Binary (..))-import Data.Bits (shiftL)+import Data.Bits (shiftL, shiftR, (.&.), (.|.)) import Data.ByteString (ByteString) import Data.ByteString qualified as B-import Data.ByteString.Char8 as C (replicate) import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Serialize (Serialize (..)) import Data.String import Data.String.Conversions (cs)-import Data.Word (Word32, Word64)+import Data.Word (Word16, Word32, Word64, Word8) import GHC.Generics (Generic) import Haskoin.Crypto.Hash import Network.Socket (SockAddr (..))@@ -83,9 +103,8 @@   deriving newtype (NFData)  instance Serial Addr where-  deserialize = Addr <$> (repList =<< deserialize)+  deserialize = Addr <$> getVarIntList action     where-      repList (VarInt c) = replicateM (fromIntegral c) action       action = liftM2 (,) getWord32le deserialize    serialize (Addr xs) = do@@ -100,6 +119,169 @@   get = deserialize   put = serialize +-- | Network that an 'addrv2' address belongs to (BIP 155).+data AddressNetwork+  = -- | IPv4, 4 address bytes+    AddrIPv4+  | -- | IPv6, 16 address bytes+    AddrIPv6+  | -- | Tor v2, 10 address bytes. No longer in use.+    AddrTorV2+  | -- | Tor v3, 32 address bytes+    AddrTorV3+  | -- | I2P, 32 address bytes+    AddrI2P+  | -- | CJDNS, 16 address bytes+    AddrCJDNS+  | -- | Yggdrasil, 16 address bytes+    AddrYggdrasil+  | -- | a network identifier this library does not know+    AddrUnknown Word8+  deriving (Eq, Show, Read, Generic, NFData)++-- | Network identifier byte of BIP 155.+addressNetworkId :: AddressNetwork -> Word8+addressNetworkId AddrIPv4 = 1+addressNetworkId AddrIPv6 = 2+addressNetworkId AddrTorV2 = 3+addressNetworkId AddrTorV3 = 4+addressNetworkId AddrI2P = 5+addressNetworkId AddrCJDNS = 6+addressNetworkId AddrYggdrasil = 7+addressNetworkId (AddrUnknown w) = w++-- | Network of a BIP 155 identifier byte.+addressNetwork :: Word8 -> AddressNetwork+addressNetwork 1 = AddrIPv4+addressNetwork 2 = AddrIPv6+addressNetwork 3 = AddrTorV2+addressNetwork 4 = AddrTorV3+addressNetwork 5 = AddrI2P+addressNetwork 6 = AddrCJDNS+addressNetwork 7 = AddrYggdrasil+addressNetwork w = AddrUnknown w++-- | Number of address bytes that BIP 155 requires for a network, if known.+addressNetworkLength :: AddressNetwork -> Maybe Int+addressNetworkLength AddrIPv4 = Just 4+addressNetworkLength AddrIPv6 = Just 16+addressNetworkLength AddrTorV2 = Just 10+addressNetworkLength AddrTorV3 = Just 32+addressNetworkLength AddrI2P = Just 32+addressNetworkLength AddrCJDNS = Just 16+addressNetworkLength AddrYggdrasil = Just 16+addressNetworkLength (AddrUnknown _) = Nothing++-- | Longest address that an 'addrv2' entry may carry, in bytes (BIP 155).+maxAddrV2Size :: Word64+maxAddrV2Size = 512++-- | An entry of an 'addrv2' message (BIP 155).+data NetworkAddressV2 = NetworkAddressV2+  { -- | time this node was last seen+    time :: !Word32,+    -- | bitmask of services available for this address+    services :: !Word64,+    -- | network the address belongs to+    network :: !AddressNetwork,+    -- | address bytes, as many as the network requires+    address :: !ByteString,+    -- | port, or 0 when the network has none+    port :: !Word16+  }+  deriving (Eq, Show, Read, Generic, NFData)++-- | An entry fails to parse when its address is longer than 'maxAddrV2Size'+-- or does not have the length its network requires. Entries of unknown+-- networks are kept with their bytes.+instance Serial NetworkAddressV2 where+  deserialize = do+    time <- getWord32le+    VarInt services <- deserialize+    network <- addressNetwork <$> getWord8+    VarInt len <- deserialize+    when (len > maxAddrV2Size) $+      fail ("addrv2 address too long: " ++ show len)+    address <- getByteString (fromIntegral len)+    forM_ (addressNetworkLength network) $ \expected ->+      unless (B.length address == expected) $+        fail $+          unwords+            [ "addrv2 address of",+              show (B.length address),+              "bytes for",+              show network,+              "(should be",+              show expected ++ ")"+            ]+    port <- getWord16be+    return NetworkAddressV2 {time, services, network, address, port}++  serialize a = do+    putWord32le a.time+    putVarInt a.services+    putWord8 (addressNetworkId a.network)+    putVarIntBytes a.address+    putWord16be a.port++instance Binary NetworkAddressV2 where+  get = deserialize+  put = serialize++instance Serialize NetworkAddressV2 where+  get = deserialize+  put = serialize++-- | The 'addrv2' message (BIP 155). It replaces 'Addr' once both peers have+-- sent @sendaddrv2@, and can carry Tor, I2P and CJDNS addresses.+newtype AddrV2 = AddrV2+  { list :: [NetworkAddressV2]+  }+  deriving (Eq, Show, Generic)+  deriving newtype (NFData)++instance Serial AddrV2 where+  deserialize = AddrV2 <$> getVarIntList deserialize+  serialize (AddrV2 xs) = putVarIntList serialize xs++instance Binary AddrV2 where+  get = deserialize+  put = serialize++instance Serialize AddrV2 where+  get = deserialize+  put = serialize++-- | Prefix of an IPv4-mapped IPv6 address.+ipv4Prefix :: ByteString+ipv4Prefix = B.replicate 10 0 <> B.pack [0xff, 0xff]++-- | The 'addrv2' form of an 'addr' entry. An IPv4-mapped address becomes an+-- 'AddrIPv4' entry with four address bytes. Any other address is IPv6.+toNetworkAddressV2 :: NetworkAddressTime -> NetworkAddressV2+toNetworkAddressV2 (t, NetworkAddress s (HostAddress bs)) =+  NetworkAddressV2 {time = t, services = s, network, address, port}+  where+    ip = B.take 16 bs+    port =+      (fromIntegral (B.index bs 16) `shiftL` 8)+        .|. fromIntegral (B.index bs 17)+    (network, address)+      | B.take 12 ip == ipv4Prefix = (AddrIPv4, B.drop 12 ip)+      | otherwise = (AddrIPv6, ip)++-- | The 'addr' form of an 'addrv2' entry. Only IPv4 and IPv6 entries have+-- one. Tor, I2P and CJDNS addresses do not fit the 'addr' message.+fromNetworkAddressV2 :: NetworkAddressV2 -> Maybe NetworkAddressTime+fromNetworkAddressV2 a =+  case a.network of+    AddrIPv4 -> Just (entry (ipv4Prefix <> a.address))+    AddrIPv6 -> Just (entry a.address)+    _ -> Nothing+  where+    entry ip = (a.time, NetworkAddress a.services (HostAddress (ip <> portBytes)))+    portBytes = B.pack [fromIntegral (a.port `shiftR` 8), fromIntegral a.port]+ -- | Data type describing signed messages that can be sent between bitcoin -- nodes to display important notifications to end users about the health of -- the network.@@ -123,6 +305,27 @@   put = serialize   get = deserialize +-- | The 'feefilter' message (BIP 133). The peer asks not to be sent+-- transactions that pay less than this fee rate.+newtype FeeFilter = FeeFilter+  { -- | minimum fee rate in satoshi per 1000 bytes+    feeRate :: Word64+  }+  deriving (Eq, Show, Read, Generic)+  deriving newtype (NFData)++instance Serial FeeFilter where+  deserialize = FeeFilter <$> getWord64le+  serialize (FeeFilter r) = putWord64le r++instance Binary FeeFilter where+  put = serialize+  get = deserialize++instance Serialize FeeFilter where+  put = serialize+  get = deserialize+ -- | The 'GetData' type is used to retrieve information on a specific object -- ('Block' or 'Tx') identified by the objects hash. The payload of a 'GetData' -- request is a list of 'InvVector' which represent all the hashes of objects@@ -138,9 +341,7 @@   deriving newtype (NFData)  instance Serial GetData where-  deserialize = GetData <$> (repList =<< deserialize)-    where-      repList (VarInt c) = replicateM (fromIntegral c) deserialize+  deserialize = GetData <$> getVarIntList deserialize    serialize (GetData xs) = do     putVarInt $ length xs@@ -165,9 +366,7 @@   deriving newtype (NFData)  instance Serial Inv where-  deserialize = Inv <$> (repList =<< deserialize)-    where-      repList (VarInt c) = replicateM (fromIntegral c) deserialize+  deserialize = Inv <$> getVarIntList deserialize    serialize (Inv xs) = do     putVarInt $ length xs@@ -192,6 +391,10 @@     InvBlock   | -- | filtered block     InvMerkleBlock+  | -- | compact block (BIP 152)+    InvCompactBlock+  | -- | transaction identified by its witness transaction id (BIP 339)+    InvWTx   | -- | segwit transaction     InvWitnessTx   | -- | segwit block@@ -211,6 +414,8 @@           1 -> return InvTx           2 -> return InvBlock           3 -> return InvMerkleBlock+          4 -> return InvCompactBlock+          5 -> return InvWTx           _             | x == 1 `shiftL` 30 + 1 -> return InvWitnessTx             | x == 1 `shiftL` 30 + 2 -> return InvWitnessBlock@@ -223,6 +428,8 @@         InvTx -> 1         InvBlock -> 2         InvMerkleBlock -> 3+        InvCompactBlock -> 4+        InvWTx -> 5         InvWitnessTx -> 1 `shiftL` 30 + 1         InvWitnessBlock -> 1 `shiftL` 30 + 2         InvWitnessMerkleBlock -> 1 `shiftL` 30 + 3@@ -259,6 +466,8 @@   get = deserialize   put = serialize +-- | Sixteen address bytes followed by a big-endian port, the 18-byte address+-- of the 'addr' and 'version' messages. newtype HostAddress   = HostAddress ByteString   deriving (Eq, Show, Ord, Generic)@@ -350,9 +559,7 @@   deriving newtype (NFData)  instance Serial NotFound where-  deserialize = NotFound <$> (repList =<< deserialize)-    where-      repList (VarInt c) = replicateM (fromIntegral c) deserialize+  deserialize = NotFound <$> getVarIntList deserialize    serialize (NotFound xs) = do     putVarInt $ length xs@@ -416,8 +623,8 @@     code :: !RejectCode,     -- | text reason for rejection     reason :: !VarString,-    -- | extra data such as block or tx hash-    extra :: !ByteString+    -- | hash of the rejected block or transaction, when the peer gives one+    extra :: !(Maybe Hash256)   }   deriving (Eq, Show, Read, Generic, NFData) @@ -469,10 +676,10 @@   put = serialize   get = deserialize --- | Convenience function to build a 'Reject' message.+-- | Convenience function to build a 'Reject' message without a hash. reject :: MessageCommand -> RejectCode -> ByteString -> Reject reject cmd code reason =-  Reject cmd code (VarString reason) B.empty+  Reject cmd code (VarString reason) Nothing  instance Serial Reject where   deserialize =@@ -480,18 +687,18 @@       Reject (stringToCommand bs)         <$> deserialize         <*> deserialize-        <*> maybeData+        <*> maybeHash     where-      maybeData =+      maybeHash =         isEmpty >>= \done ->           if done-            then return B.empty-            else getByteString 32-  serialize (Reject cmd code reason dat) = do+            then return Nothing+            else Just <$> deserialize+  serialize (Reject cmd code reason extra) = do     serialize $ VarString $ commandToString cmd     serialize code     serialize reason-    unless (B.null dat) $ putByteString dat+    forM_ extra serialize  instance Binary Reject where   put = serialize@@ -501,19 +708,61 @@   put = serialize   get = deserialize +-- | The 'sendtxrcncl' message (BIP 330). It announces support for+-- transaction reconciliation.+data SendTxRcncl = SendTxRcncl+  { -- | reconciliation protocol version+    version :: !Word32,+    -- | salt for the short transaction identifiers+    salt :: !Word64+  }+  deriving (Eq, Show, Read, Generic, NFData)++instance Serial SendTxRcncl where+  deserialize = SendTxRcncl <$> getWord32le <*> getWord64le+  serialize (SendTxRcncl v s) = putWord32le v >> putWord64le s++instance Binary SendTxRcncl where+  put = serialize+  get = deserialize++instance Serialize SendTxRcncl where+  put = serialize+  get = deserialize+ -- | Data type representing a variable-length integer. The 'VarInt' type -- usually precedes an array or a string that can vary in length. newtype VarInt = VarInt {get :: Word64}   deriving (Eq, Show, Read, Generic)   deriving newtype (NFData) +-- | Only the shortest encoding of a value is accepted, as in the+-- @ReadCompactSize@ function of Bitcoin Core. A value below 253 must use one+-- byte, below 0x10000 three bytes, and below 0x100000000 five bytes. instance Serial VarInt where   deserialize = VarInt <$> (getWord8 >>= go)     where-      go 0xff = getWord64le-      go 0xfe = fromIntegral <$> getWord32le-      go 0xfd = fromIntegral <$> getWord16le+      go 0xff = do+        x <- getWord64le+        when (x < 0x100000000) $ nonCanonical 9 x+        return x+      go 0xfe = do+        x <- getWord32le+        when (x < 0x10000) $ nonCanonical 5 x+        return $ fromIntegral x+      go 0xfd = do+        x <- getWord16le+        when (x < 0xfd) $ nonCanonical 3 x+        return $ fromIntegral x       go x = return $ fromIntegral x+      nonCanonical :: (MonadGet m, Show a) => Int -> a -> m ()+      nonCanonical n x =+        fail $+          unwords+            [ "VarInt: non-canonical",+              show n ++ "-byte encoding of",+              show x+            ]    serialize (VarInt x)     | x < 0xfd =@@ -539,19 +788,51 @@ putVarInt :: (MonadPut m, Integral a) => a -> m () putVarInt = serialize . VarInt . fromIntegral +-- | Largest number of elements that a serialized list may announce, and+-- largest number of bytes that a serialized string may announce. Bitcoin Core+-- refuses larger counts when it reads a compact size (@MAX_SIZE@).+maxListSize :: Word64+maxListSize = 0x02000000++-- | Read a list that starts with its element count as a 'VarInt'. A count+-- above 'maxListSize' fails the parse. Without this check a huge count would+-- wrap around to a negative 'Int' and read as an empty list.+getVarIntList :: (MonadGet m) => m a -> m [a]+getVarIntList element = do+  VarInt count <- deserialize+  when (count > maxListSize) $+    fail ("List count too large: " ++ show count)+  replicateM (fromIntegral count) element++-- | Write a list with its element count as a 'VarInt' in front.+putVarIntList :: (MonadPut m) => (a -> m ()) -> [a] -> m ()+putVarIntList element xs = do+  putVarInt (length xs)+  mapM_ element xs++-- | Read bytes that start with their count as a 'VarInt'. A count above+-- 'maxListSize' fails the parse.+getVarIntBytes :: (MonadGet m) => m ByteString+getVarIntBytes = do+  VarInt count <- deserialize+  when (count > maxListSize) $+    fail ("Byte count too large: " ++ show count)+  getByteString (fromIntegral count)++-- | Write bytes with their count as a 'VarInt' in front.+putVarIntBytes :: (MonadPut m) => ByteString -> m ()+putVarIntBytes bs = do+  putVarInt (B.length bs)+  putByteString bs+ -- | Data type for serialization of variable-length strings. newtype VarString = VarString {get :: ByteString}   deriving (Eq, Show, Read, Generic)   deriving newtype (NFData)  instance Serial VarString where-  deserialize = VarString <$> (readBS =<< deserialize)-    where-      readBS (VarInt len) = getByteString (fromIntegral len)--  serialize (VarString bs) = do-    putVarInt $ B.length bs-    putByteString bs+  deserialize = VarString <$> getVarIntBytes+  serialize (VarString bs) = putVarIntBytes bs  instance Binary VarString where   put = serialize@@ -561,6 +842,11 @@   put = serialize   get = deserialize +-- | Longest user agent that a 'Version' message may carry, in bytes. Bitcoin+-- Core disconnects a peer that sends a longer one (@MAX_SUBVERSION_LENGTH@).+maxUserAgentLength :: Int+maxUserAgentLength = 256+ -- | When a bitcoin node creates an outgoing connection to another node, -- the first message it will send is a 'Version' message. The other node -- will similarly respond with it's own 'Version' message.@@ -586,6 +872,9 @@   }   deriving (Eq, Show, Generic, NFData) +-- | A user agent longer than 'maxUserAgentLength' fails the parse. The relay+-- flag is 'True' when the message ends before it, as for peers that predate+-- BIP 37. instance Serial Version where   deserialize =     Version@@ -595,12 +884,17 @@       <*> deserialize       <*> deserialize       <*> getWord64le-      <*> deserialize+      <*> userAgent       <*> getWord32le       <*> (go =<< isEmpty)     where       go True = return True       go False = getBool+      userAgent = do+        ua@(VarString bs) <- deserialize+        when (B.length bs > maxUserAgentLength) $+          fail ("Version: user agent of " ++ show (B.length bs) ++ " bytes is too long")+        return ua    serialize (Version v s t ar as n ua sh r) = do     putWord32le v@@ -641,6 +935,8 @@   = MCVersion   | MCVerAck   | MCAddr+  | MCAddrV2+  | MCSendAddrV2   | MCInv   | MCGetData   | MCNotFound@@ -660,6 +956,19 @@   | MCMempool   | MCReject   | MCSendHeaders+  | MCFeeFilter+  | MCSendCmpct+  | MCCmpctBlock+  | MCGetBlockTxn+  | MCBlockTxn+  | MCGetCFilters+  | MCCFilter+  | MCGetCFHeaders+  | MCCFHeaders+  | MCGetCFCheckpt+  | MCCFCheckpt+  | MCWtxidRelay+  | MCSendTxRcncl   | MCOther ByteString   deriving (Eq, Generic, NFData) @@ -671,12 +980,15 @@     String str <- lexP     return (stringToCommand (cs str)) +-- | A command is twelve bytes on the wire: its name in printable ASCII, then+-- NUL bytes. The parse fails for a byte outside printable ASCII in the name or+-- a byte other than NUL after it, as @IsMessageTypeValid@ does in Bitcoin+-- Core. Serializing a name longer than twelve bytes, or with a byte outside+-- printable ASCII, is an error. instance Serial MessageCommand where-  deserialize = go <$> getByteString 12-    where-      go bs =-        let str = unpackCommand bs-         in stringToCommand str+  deserialize = do+    bs <- getByteString 12+    either fail (return . stringToCommand) (unpackCommand bs)   serialize mc = putByteString $ packCommand $ commandToString mc  instance Binary MessageCommand where@@ -696,6 +1008,8 @@   "version" -> MCVersion   "verack" -> MCVerAck   "addr" -> MCAddr+  "addrv2" -> MCAddrV2+  "sendaddrv2" -> MCSendAddrV2   "inv" -> MCInv   "getdata" -> MCGetData   "notfound" -> MCNotFound@@ -715,6 +1029,19 @@   "mempool" -> MCMempool   "reject" -> MCReject   "sendheaders" -> MCSendHeaders+  "feefilter" -> MCFeeFilter+  "sendcmpct" -> MCSendCmpct+  "cmpctblock" -> MCCmpctBlock+  "getblocktxn" -> MCGetBlockTxn+  "blocktxn" -> MCBlockTxn+  "getcfilters" -> MCGetCFilters+  "cfilter" -> MCCFilter+  "getcfheaders" -> MCGetCFHeaders+  "cfheaders" -> MCCFHeaders+  "getcfcheckpt" -> MCGetCFCheckpt+  "cfcheckpt" -> MCCFCheckpt+  "wtxidrelay" -> MCWtxidRelay+  "sendtxrcncl" -> MCSendTxRcncl   _ -> MCOther str  -- | Convert a 'MessageCommand' to its string representation.@@ -723,6 +1050,8 @@   MCVersion -> "version"   MCVerAck -> "verack"   MCAddr -> "addr"+  MCAddrV2 -> "addrv2"+  MCSendAddrV2 -> "sendaddrv2"   MCInv -> "inv"   MCGetData -> "getdata"   MCNotFound -> "notfound"@@ -742,17 +1071,51 @@   MCMempool -> "mempool"   MCReject -> "reject"   MCSendHeaders -> "sendheaders"+  MCFeeFilter -> "feefilter"+  MCSendCmpct -> "sendcmpct"+  MCCmpctBlock -> "cmpctblock"+  MCGetBlockTxn -> "getblocktxn"+  MCBlockTxn -> "blocktxn"+  MCGetCFilters -> "getcfilters"+  MCCFilter -> "cfilter"+  MCGetCFHeaders -> "getcfheaders"+  MCCFHeaders -> "cfheaders"+  MCGetCFCheckpt -> "getcfcheckpt"+  MCCFCheckpt -> "cfcheckpt"+  MCWtxidRelay -> "wtxidrelay"+  MCSendTxRcncl -> "sendtxrcncl"   MCOther c -> c --- | Pack a string 'MessageCommand' so that it is exactly 12-bytes long.+-- | Length of a command on the wire.+commandSize :: Int+commandSize = 12++-- | Is this byte printable ASCII? Command names are made of these.+printable :: Word8 -> Bool+printable w = w >= 0x20 && w <= 0x7e++-- | Pack a command name so that it is exactly 12 bytes long. A name that+-- does not fit, or has a byte outside printable ASCII, is an error. Bitcoin+-- Core asserts the same when it builds a header. packCommand :: ByteString -> ByteString-packCommand s =-  B.take 12 $-    s `mappend` C.replicate 12 '\NUL'+packCommand s+  | B.length s > commandSize =+      error ("Message command longer than 12 bytes: " ++ show s)+  | not (B.all printable s) =+      error ("Message command with a byte outside printable ASCII: " ++ show s)+  | otherwise = s <> B.replicate (commandSize - B.length s) 0 --- | Undo packing done by 'packCommand'.-unpackCommand :: ByteString -> ByteString-unpackCommand = B.takeWhile (/= 0)+-- | Undo packing done by 'packCommand'. Fails for a name with a byte outside+-- printable ASCII, or for a byte other than NUL after the name.+unpackCommand :: ByteString -> Either String ByteString+unpackCommand bs+  | not (B.all (== 0) padding) =+      Left ("Message command with bytes after its NUL terminator: " ++ show bs)+  | not (B.all printable name) =+      Left ("Message command with a byte outside printable ASCII: " ++ show bs)+  | otherwise = Right name+  where+    (name, padding) = B.break (== 0) bs  -- | Node offers no services. nodeNone :: Word64@@ -777,3 +1140,17 @@ -- | Services indicate Xtreme Thinblocks compatibility. nodeXThin :: Word64 nodeXThin = 1 `shiftL` 4++-- | Services indicate node serves compact block filters (BIP 157).+nodeCompactFilters :: Word64+nodeCompactFilters = 1 `shiftL` 6++-- | Services indicate node serves only the last 288 blocks (BIP 159). Pruned+-- nodes advertise this instead of 'nodeNetwork'.+nodeNetworkLimited :: Word64+nodeNetworkLimited = 1 `shiftL` 10++-- | Services indicate node supports the version 2 encrypted transport+-- (BIP 324).+nodeP2PV2 :: Word64+nodeP2PV2 = 1 `shiftL` 11
+ src/Haskoin/Network/CompactBlock.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE NoFieldSelectors #-}++-- |+-- Module      : Haskoin.Network.CompactBlock+-- Copyright   : No rights reserved+-- License     : MIT+-- Maintainer  : jprupp@protonmail.ch+-- Stability   : experimental+-- Portability : POSIX+--+-- Messages of the compact block relay protocol (BIP 152): @sendcmpct@,+-- @cmpctblock@, @getblocktxn@ and @blocktxn@.+module Haskoin.Network.CompactBlock+  ( SendCompact (..),+    CompactBlock (..),+    PrefilledTx (..),+    GetBlockTxn (..),+    BlockTxn (..),+    maxShortId,+  )+where++import Control.DeepSeq+import Control.Monad (when)+import Data.Binary (Binary (..))+import Data.Bits (shiftL, shiftR, (.|.))+import Data.Bytes.Get+import Data.Bytes.Put+import Data.Bytes.Serial+import Data.Serialize (Serialize (..))+import Data.Word (Word32, Word64)+import GHC.Generics (Generic)+import Haskoin.Block.Common (BlockHash, BlockHeader)+import Haskoin.Network.Common+import Haskoin.Transaction.Common (Tx)++-- | The @sendcmpct@ message. It tells the peer whether to announce new blocks+-- as compact blocks, and which version of the protocol to use. Version 1+-- identifies transactions by their txid and version 2 by their wtxid.+data SendCompact = SendCompact+  { -- | announce blocks with @cmpctblock@ instead of @inv@ or @headers@+    announce :: !Bool,+    -- | compact block protocol version+    version :: !Word64+  }+  deriving (Eq, Show, Read, Generic, NFData)++instance Serial SendCompact where+  deserialize = SendCompact <$> ((/= 0) <$> getWord8) <*> getWord64le+  serialize (SendCompact a v) = do+    putWord8 (if a then 1 else 0)+    putWord64le v++instance Binary SendCompact where+  put = serialize+  get = deserialize++instance Serialize SendCompact where+  put = serialize+  get = deserialize++-- | Largest short transaction identifier. Short identifiers are six bytes.+maxShortId :: Word64+maxShortId = 0xffffffffffff++getShortId :: (MonadGet m) => m Word64+getShortId = do+  low <- getWord32le+  high <- getWord16le+  return $ fromIntegral high `shiftL` 32 .|. fromIntegral low++putShortId :: (MonadPut m) => Word64 -> m ()+putShortId x+  | x > maxShortId = error ("Short transaction id does not fit six bytes: " ++ show x)+  | otherwise = do+      putWord32le (fromIntegral x)+      putWord16le (fromIntegral (x `shiftR` 32))++-- | A transaction sent along with a compact block, with its position in the+-- block.+data PrefilledTx = PrefilledTx+  { -- | position of the transaction in the block+    index :: !Word32,+    -- | the transaction+    tx :: !Tx+  }+  deriving (Eq, Show, Generic, NFData)++-- | The @cmpctblock@ message: a block header with short identifiers for most+-- of its transactions, and the full transactions the sender expects the peer+-- to lack. Positions in the block are absolute here; on the wire each one is+-- encoded as its distance from the previous one, as BIP 152 requires.+data CompactBlock = CompactBlock+  { -- | block header+    header :: !BlockHeader,+    -- | nonce the short identifiers were computed with+    nonce :: !Word64,+    -- | short identifiers of the transactions not sent in full+    shortIds :: ![Word64],+    -- | transactions sent in full, in order of their position+    prefilled :: ![PrefilledTx]+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial CompactBlock where+  deserialize =+    CompactBlock+      <$> deserialize+      <*> getWord64le+      <*> getVarIntList getShortId+      <*> getDifferentialList (\i -> PrefilledTx i <$> deserialize)++  serialize c = do+    serialize c.header+    putWord64le c.nonce+    putVarIntList putShortId c.shortIds+    putDifferentialList (.index) (serialize . (.tx)) c.prefilled++instance Binary CompactBlock where+  put = serialize+  get = deserialize++instance Serialize CompactBlock where+  put = serialize+  get = deserialize++-- | The @getblocktxn@ message. It asks for the transactions at the given+-- positions of a block. Positions are absolute here and differentially+-- encoded on the wire.+data GetBlockTxn = GetBlockTxn+  { -- | hash of the block+    hash :: !BlockHash,+    -- | positions of the wanted transactions, in increasing order+    indexes :: ![Word32]+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial GetBlockTxn where+  deserialize = GetBlockTxn <$> deserialize <*> getDifferentialList return+  serialize g = do+    serialize g.hash+    putDifferentialList id (const (return ())) g.indexes++instance Binary GetBlockTxn where+  put = serialize+  get = deserialize++instance Serialize GetBlockTxn where+  put = serialize+  get = deserialize++-- | The @blocktxn@ message, the answer to 'GetBlockTxn'.+data BlockTxn = BlockTxn+  { -- | hash of the block+    hash :: !BlockHash,+    -- | the requested transactions, in the order they were asked for+    transactions :: ![Tx]+  }+  deriving (Eq, Show, Generic, NFData)++instance Serial BlockTxn where+  deserialize = BlockTxn <$> deserialize <*> getVarIntList deserialize+  serialize b = do+    serialize b.hash+    putVarIntList serialize b.transactions++instance Binary BlockTxn where+  put = serialize+  get = deserialize++instance Serialize BlockTxn where+  put = serialize+  get = deserialize++-- | Read a list whose elements each start with a block position. The first+-- position is written as is, and every later one as its distance from the+-- previous position minus one. A position that does not fit 32 bits fails+-- the parse.+getDifferentialList :: (MonadGet m) => (Word32 -> m a) -> m [a]+getDifferentialList element = do+  VarInt count <- deserialize+  when (count > maxListSize) $+    fail ("List count too large: " ++ show count)+  go count Nothing+  where+    go 0 _ = return []+    go n previous = do+      VarInt distance <- deserialize+      let position = toInteger distance + maybe 0 (\p -> toInteger p + 1) previous+      when (position > 0xffffffff) $+        fail ("Block position does not fit 32 bits: " ++ show position)+      let i = fromInteger position+      x <- element i+      (x :) <$> go (n - 1) (Just i)++-- | Write a list whose elements each carry a block position, in the encoding+-- that 'getDifferentialList' reads. Positions must be strictly increasing,+-- otherwise their distances cannot be encoded and this is an error.+putDifferentialList :: (MonadPut m) => (a -> Word32) -> (a -> m ()) -> [a] -> m ()+putDifferentialList position element xs = do+  putVarInt (length xs)+  go Nothing xs+  where+    go _ [] = return ()+    go previous (x : rest) = do+      let i = position x+      case previous of+        Nothing -> putVarInt i+        Just p+          | i > p -> putVarInt (i - p - 1)+          | otherwise ->+              error ("Block positions must be strictly increasing: " ++ show p ++ " then " ++ show i)+      element x+      go (Just i) rest
src/Haskoin/Network/Constants.hs view
@@ -81,7 +81,9 @@           0x1d00ffff           2083236893,       -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f+      genesisTx = mainnetGenesisTx,       maxBlockSize = 1000000,+      maxMessageSize = 4000000,       maxSatoshi = 2100000000000000,       userAgent =         "/haskoin-btc:" <> versionString <> "/",@@ -91,10 +93,8 @@       bip94 = False,       powLimit =         0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 227931,-          "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"-        ),+      bip34Height = 227931,+      bip34Hash = Just "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8",       bip65Height = 388381,       bip66Height = 363725,       targetTimespan = 14 * 24 * 60 * 60,@@ -182,7 +182,9 @@           486604799           414098458,       -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943+      genesisTx = mainnetGenesisTx,       maxBlockSize = 1000000,+      maxMessageSize = 4000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-btc-test:" <> versionString <> "/",       defaultPort = 18333,@@ -191,10 +193,8 @@       bip94 = False,       powLimit =         0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 21111,-          "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"-        ),+      bip34Height = 21111,+      bip34Hash = Just "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8",       bip65Height = 581885,       bip66Height = 330776,       targetTimespan = 14 * 24 * 60 * 60,@@ -243,7 +243,9 @@           0x1d00ffff           393743547,       -- Hash 00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043+      genesisTx = testnet4GenesisTx,       maxBlockSize = 1000000,+      maxMessageSize = 4000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-btc-test4:" <> versionString <> "/",       defaultPort = 48333,@@ -252,10 +254,8 @@       bip94 = True,       powLimit =         0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 1,-          "0000000012982b6d5f621229286b880e909984df669c2afabb102ce311b13f28"-        ),+      bip34Height = 1,+      bip34Hash = Just "0000000012982b6d5f621229286b880e909984df669c2afabb102ce311b13f28",       bip65Height = 1,       bip66Height = 1,       targetTimespan = 14 * 24 * 60 * 60,@@ -298,7 +298,9 @@           1296688602           0x207fffff           2,+      genesisTx = mainnetGenesisTx,       maxBlockSize = 1000000,+      maxMessageSize = 4000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-btc-regtest:" <> versionString <> "/",       defaultPort = 18444,@@ -307,12 +309,10 @@       bip94 = False,       powLimit =         0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 100000000,-          "0000000000000000000000000000000000000000000000000000000000000000"-        ),-      bip65Height = 1351,-      bip66Height = 1251,+      bip34Height = 1,+      bip34Hash = Nothing,+      bip65Height = 1,+      bip66Height = 1,       targetTimespan = 14 * 24 * 60 * 60,       targetSpacing = 10 * 60,       checkpoints = [],@@ -350,7 +350,9 @@           0x1d00ffff           2083236893,       -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f+      genesisTx = mainnetGenesisTx,       maxBlockSize = 32000000,+      maxMessageSize = 64000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-bch:" <> versionString <> "/",       defaultPort = 8333,@@ -359,10 +361,8 @@       bip94 = False,       powLimit =         0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 227931,-          "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"-        ),+      bip34Height = 227931,+      bip34Hash = Just "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8",       bip65Height = 388381,       bip66Height = 363725,       targetTimespan = 14 * 24 * 60 * 60,@@ -416,10 +416,20 @@            ( 504031,             "0000000000000000011ebf65b60d0a3de80b8175be709d653b4c1a1beeb6ab9c"+          ),+          ( 951145,+            "000000000000000000f64dbed68370486f945097ba90b17e30d7bf5c43df7a60"+          ),+          ( 960710,+            "00000000000000000275ee55c04aa109347c9ee0b5bb4617837df3044f7e1be5"+          ),+          ( 970892,+            "000000000000000000e76270e57f4ebbd2b282db46a1695984935126c0a68cd8"           )         ],       seeds =         [ "seed.flowee.cash", -- Flowee+          "seed-bch.bitcoinforks.org", -- Bitcoin Forks           "btccash-seeder.bitcoinunlimited.info", -- Bitcoin Unlimited           "seed.bchd.cash", -- BCHD           "seed.bch.loping.net", -- Loping.net@@ -459,7 +469,9 @@           0x1d00ffff           114152193,       -- Hash 000000001dd410c49a788668ce26751718cc797474d3152a5fc073dd44fd9f7b+      genesisTx = mainnetGenesisTx,       maxBlockSize = 2000000,+      maxMessageSize = 4000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-bch-test4:" <> versionString <> "/",       defaultPort = 28333,@@ -468,10 +480,8 @@       bip94 = False,       powLimit =         0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 2,-          "00000000b0c65b1e03baace7d5c093db0d6aac224df01484985ffd5e86a1a20c"-        ),+      bip34Height = 2,+      bip34Hash = Just "00000000b0c65b1e03baace7d5c093db0d6aac224df01484985ffd5e86a1a20c",       bip65Height = 3,       bip66Height = 4,       targetTimespan = 14 * 24 * 60 * 60,@@ -490,6 +500,15 @@           ),           ( 54700,             "00000000009af4379d87f17d0f172ee4769b48839a5a3a3e81d69da4322518b8"+          ),+          ( 305848,+            "00000000601bf68f9927154cf55557b1c08245b4d415658d1cbcd1eb4502dabc"+          ),+          ( 313651,+            "000000005c58d20403191a778c51206eca7552629c1e390313e786d2c388d0fb"+          ),+          ( 325709,+            "00000000000097a6ca206aa88b45a2158eea1a9ab8a49aed8f4b193d558e74b4"           )         ],       seeds =@@ -531,7 +550,9 @@           486604799           414098458,       -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943+      genesisTx = mainnetGenesisTx,       maxBlockSize = 32000000,+      maxMessageSize = 64000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-bch-test:" <> versionString <> "/",       defaultPort = 18333,@@ -540,10 +561,8 @@       bip94 = False,       powLimit =         0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 21111,-          "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"-        ),+      bip34Height = 21111,+      bip34Hash = Just "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8",       bip65Height = 581885,       bip66Height = 330776,       targetTimespan = 14 * 24 * 60 * 60,@@ -561,6 +580,15 @@            ( 1188697,             "0000000000170ed0918077bde7b4d36cc4c91be69fa09211f748240dabe047fb"+          ),+          ( 1710483,+            "00000000078fe0c74ffa0c5221081c34774aa7d17a3c14c36f952f829463f120"+          ),+          ( 1718198,+            "00000000fb70d93bc600b9189f2bb13383b0e44d96a4547615b3c194680e6308"+          ),+          ( 1730264,+            "000000000001ce982fa29717a4ed233b0e5a7a8b9936a17fdeeccc5c1d0af795"           )         ],       seeds =@@ -601,7 +629,9 @@           1296688602           0x207fffff           2,-      maxBlockSize = 1000000,+      genesisTx = mainnetGenesisTx,+      maxBlockSize = 32000000,+      maxMessageSize = 64000000,       maxSatoshi = 2100000000000000,       userAgent = "/haskoin-bch-regtest:" <> versionString <> "/",       defaultPort = 18444,@@ -610,10 +640,8 @@       bip94 = False,       powLimit =         0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-      bip34Block =-        ( 100000000,-          "0000000000000000000000000000000000000000000000000000000000000000"-        ),+      bip34Height = 100000000,+      bip34Hash = Nothing,       bip65Height = 1351,       bip66Height = 1251,       targetTimespan = 14 * 24 * 60 * 60,
src/Haskoin/Network/Data.hs view
@@ -15,6 +15,7 @@ import Data.Word (Word32, Word64, Word8) import GHC.Generics (Generic) import Haskoin.Block.Common+import Haskoin.Transaction.Common (Tx)  -- | Network definition. data Network = Network@@ -34,8 +35,14 @@     magic :: !Word32,     -- | genesis block header     genesisHeader :: !BlockHeader,+    -- | coinbase transaction of the genesis block+    genesisTx :: !Tx,     -- | maximum block size in bytes     maxBlockSize :: !Int,+    -- | largest P2P message that 'getMessage' accepts, in bytes. Bitcoin Core+    -- refuses any message over 4,000,000 bytes (@MAX_PROTOCOL_MESSAGE_LENGTH@).+    -- Bitcoin Cash Node refuses block messages over twice the block size.+    maxMessageSize :: !Word32,     -- | maximum amount of satoshi     maxSatoshi :: !Word64,     -- | user agent string@@ -50,8 +57,11 @@     bip94 :: !Bool,     -- | proof-of-work target higest possible value     powLimit :: !Integer,-    -- | block at which BIP34 activates-    bip34Block :: !(BlockHeight, BlockHash),+    -- | height at which BIP34 activates+    bip34Height :: !BlockHeight,+    -- | hash of the block at 'bip34Height', when the chain pins one. Bitcoin+    -- Core pins none on regtest.+    bip34Hash :: !(Maybe BlockHash),     -- | block at which BIP65 activates     bip65Height :: !BlockHeight,     -- | block at which BIP66 activates
src/Haskoin/Network/Message.hs view
@@ -25,7 +25,7 @@ where  import Control.DeepSeq-import Control.Monad (unless)+import Control.Monad (unless, when) import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import Data.ByteString qualified as B@@ -38,8 +38,10 @@ import Haskoin.Block.Common import Haskoin.Block.Merkle import Haskoin.Crypto.Hash+import Haskoin.Network.BlockFilter import Haskoin.Network.Bloom import Haskoin.Network.Common+import Haskoin.Network.CompactBlock import Haskoin.Network.Data import Haskoin.Transaction.Common @@ -89,6 +91,8 @@   = MVersion !Version   | MVerAck   | MAddr !Addr+  | MAddrV2 !AddrV2+  | MSendAddrV2   | MInv !Inv   | MGetData !GetData   | MNotFound !NotFound@@ -108,7 +112,21 @@   | MMempool   | MReject !Reject   | MSendHeaders-  | MOther !ByteString !ByteString+  | MFeeFilter !FeeFilter+  | MSendCompact !SendCompact+  | MCompactBlock !CompactBlock+  | MGetBlockTxn !GetBlockTxn+  | MBlockTxn !BlockTxn+  | MGetCFilters !GetCFilters+  | MCFilter !CFilter+  | MGetCFHeaders !GetCFHeaders+  | MCFHeaders !CFHeaders+  | MGetCFCheckpt !GetCFCheckpt+  | MCFCheckpt !CFCheckpt+  | MWtxidRelay+  | MSendTxRcncl !SendTxRcncl+  | -- | a message of a command this library does not know, with its payload+    MOther !ByteString !ByteString   deriving (Eq, Show, Generic, NFData)  -- | Get 'MessageCommand' assocated with a message.@@ -116,6 +134,8 @@ msgType (MVersion _) = MCVersion msgType MVerAck = MCVerAck msgType (MAddr _) = MCAddr+msgType (MAddrV2 _) = MCAddrV2+msgType MSendAddrV2 = MCSendAddrV2 msgType (MInv _) = MCInv msgType (MGetData _) = MCGetData msgType (MNotFound _) = MCNotFound@@ -125,6 +145,7 @@ msgType (MBlock _) = MCBlock msgType (MMerkleBlock _) = MCMerkleBlock msgType (MHeaders _) = MCHeaders+msgType MGetAddr = MCGetAddr msgType (MFilterLoad _) = MCFilterLoad msgType (MFilterAdd _) = MCFilterAdd msgType MFilterClear = MCFilterClear@@ -134,61 +155,89 @@ msgType MMempool = MCMempool msgType (MReject _) = MCReject msgType MSendHeaders = MCSendHeaders-msgType MGetAddr = MCGetAddr+msgType (MFeeFilter _) = MCFeeFilter+msgType (MSendCompact _) = MCSendCmpct+msgType (MCompactBlock _) = MCCmpctBlock+msgType (MGetBlockTxn _) = MCGetBlockTxn+msgType (MBlockTxn _) = MCBlockTxn+msgType (MGetCFilters _) = MCGetCFilters+msgType (MCFilter _) = MCCFilter+msgType (MGetCFHeaders _) = MCGetCFHeaders+msgType (MCFHeaders _) = MCCFHeaders+msgType (MGetCFCheckpt _) = MCGetCFCheckpt+msgType (MCFCheckpt _) = MCCFCheckpt+msgType MWtxidRelay = MCWtxidRelay+msgType (MSendTxRcncl _) = MCSendTxRcncl msgType (MOther c _) = MCOther c --- | Deserializer for network messages.+-- | Deserializer for network messages. It reads one message and nothing+-- more, so messages can be read one after the other from a stream. The parse+-- fails for a header with the magic of another network, a payload larger+-- than the 'maxMessageSize' of the network, or a checksum that does not+-- match the payload. Only the declared payload is handed to the parser of the+-- message type. A message type that carries no payload is accepted with+-- whatever payload it comes with, as in Bitcoin Core. getMessage :: (MonadGet m) => Network -> m Message getMessage net = do   (MessageHeader mgc cmd len chk) <- deserialize-  bs <- lookAhead $ getByteString $ fromIntegral len   unless     (mgc == net.magic)     (fail $ "get: Invalid network magic bytes: " ++ show mgc)+  when (len > net.maxMessageSize) $+    fail $+      "get: Message of "+        ++ show len+        ++ " bytes is larger than the limit of "+        ++ show net.maxMessageSize+  payload <- getByteString (fromIntegral len)   unless-    (checkSum32 bs == chk)+    (checkSum32 payload == chk)     (fail $ "get: Invalid message checksum: " ++ show chk)-  if len > 0-    then do-      bs <- ensure (fromIntegral len)-      let f = case cmd of-            MCVersion -> MVersion <$> deserialize-            MCAddr -> MAddr <$> deserialize-            MCInv -> MInv <$> deserialize-            MCGetData -> MGetData <$> deserialize-            MCNotFound -> MNotFound <$> deserialize-            MCGetBlocks -> MGetBlocks <$> deserialize-            MCGetHeaders -> MGetHeaders <$> deserialize-            MCTx -> MTx <$> deserialize-            MCBlock -> MBlock <$> deserialize-            MCMerkleBlock -> MMerkleBlock <$> deserialize-            MCHeaders -> MHeaders <$> deserialize-            MCFilterLoad -> MFilterLoad <$> deserialize-            MCFilterAdd -> MFilterAdd <$> deserialize-            MCPing -> MPing <$> deserialize-            MCPong -> MPong <$> deserialize-            MCAlert -> MAlert <$> deserialize-            MCReject -> MReject <$> deserialize-            MCOther c -> MOther c <$> getByteString (fromIntegral len)-            _ ->-              fail $-                "get: command "-                  ++ show cmd-                  ++ " should not carry a payload"-      either fail return (runGetS f bs)-    else case cmd of-      MCGetAddr -> return MGetAddr-      MCVerAck -> return MVerAck-      MCFilterClear -> return MFilterClear-      MCMempool -> return MMempool-      MCSendHeaders -> return MSendHeaders-      MCOther c -> return (MOther c B.empty)-      _ ->-        fail $-          "get: command "-            ++ show cmd-            ++ " is expected to carry a payload"+  either fail return (runGetS (getPayload cmd len) payload) +-- | Parser for the payload of a message of the given command.+getPayload :: (MonadGet m) => MessageCommand -> Word32 -> m Message+getPayload cmd len =+  case cmd of+    MCVersion -> MVersion <$> deserialize+    MCVerAck -> return MVerAck+    MCAddr -> MAddr <$> deserialize+    MCAddrV2 -> MAddrV2 <$> deserialize+    MCSendAddrV2 -> return MSendAddrV2+    MCInv -> MInv <$> deserialize+    MCGetData -> MGetData <$> deserialize+    MCNotFound -> MNotFound <$> deserialize+    MCGetBlocks -> MGetBlocks <$> deserialize+    MCGetHeaders -> MGetHeaders <$> deserialize+    MCTx -> MTx <$> deserialize+    MCBlock -> MBlock <$> deserialize+    MCMerkleBlock -> MMerkleBlock <$> deserialize+    MCHeaders -> MHeaders <$> deserialize+    MCGetAddr -> return MGetAddr+    MCFilterLoad -> MFilterLoad <$> deserialize+    MCFilterAdd -> MFilterAdd <$> deserialize+    MCFilterClear -> return MFilterClear+    MCPing -> MPing <$> deserialize+    MCPong -> MPong <$> deserialize+    MCAlert -> MAlert <$> deserialize+    MCMempool -> return MMempool+    MCReject -> MReject <$> deserialize+    MCSendHeaders -> return MSendHeaders+    MCFeeFilter -> MFeeFilter <$> deserialize+    MCSendCmpct -> MSendCompact <$> deserialize+    MCCmpctBlock -> MCompactBlock <$> deserialize+    MCGetBlockTxn -> MGetBlockTxn <$> deserialize+    MCBlockTxn -> MBlockTxn <$> deserialize+    MCGetCFilters -> MGetCFilters <$> deserialize+    MCCFilter -> MCFilter <$> deserialize+    MCGetCFHeaders -> MGetCFHeaders <$> deserialize+    MCCFHeaders -> MCFHeaders <$> deserialize+    MCGetCFCheckpt -> MGetCFCheckpt <$> deserialize+    MCCFCheckpt -> MCFCheckpt <$> deserialize+    MCWtxidRelay -> return MWtxidRelay+    MCSendTxRcncl -> MSendTxRcncl <$> deserialize+    MCOther c -> MOther c <$> getByteString (fromIntegral len)+ -- | Serializer for network messages. putMessage :: (MonadPut m) => Network -> Message -> m () putMessage net msg = do@@ -197,6 +246,8 @@           MVersion m -> (MCVersion, runPutS $ serialize m)           MVerAck -> (MCVerAck, B.empty)           MAddr m -> (MCAddr, runPutS $ serialize m)+          MAddrV2 m -> (MCAddrV2, runPutS $ serialize m)+          MSendAddrV2 -> (MCSendAddrV2, B.empty)           MInv m -> (MCInv, runPutS $ serialize m)           MGetData m -> (MCGetData, runPutS $ serialize m)           MNotFound m -> (MCNotFound, runPutS $ serialize m)@@ -216,6 +267,19 @@           MMempool -> (MCMempool, B.empty)           MReject m -> (MCReject, runPutS $ serialize m)           MSendHeaders -> (MCSendHeaders, B.empty)+          MFeeFilter m -> (MCFeeFilter, runPutS $ serialize m)+          MSendCompact m -> (MCSendCmpct, runPutS $ serialize m)+          MCompactBlock m -> (MCCmpctBlock, runPutS $ serialize m)+          MGetBlockTxn m -> (MCGetBlockTxn, runPutS $ serialize m)+          MBlockTxn m -> (MCBlockTxn, runPutS $ serialize m)+          MGetCFilters m -> (MCGetCFilters, runPutS $ serialize m)+          MCFilter m -> (MCCFilter, runPutS $ serialize m)+          MGetCFHeaders m -> (MCGetCFHeaders, runPutS $ serialize m)+          MCFHeaders m -> (MCCFHeaders, runPutS $ serialize m)+          MGetCFCheckpt m -> (MCGetCFCheckpt, runPutS $ serialize m)+          MCFCheckpt m -> (MCCFCheckpt, runPutS $ serialize m)+          MWtxidRelay -> (MCWtxidRelay, B.empty)+          MSendTxRcncl m -> (MCSendTxRcncl, runPutS $ serialize m)           MOther c p -> (MCOther c, p)       chk = checkSum32 payload       len = fromIntegral $ B.length payload
src/Haskoin/Script/Common.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE NoFieldSelectors #-}  -- |@@ -18,9 +19,12 @@ module Haskoin.Script.Common   ( -- * Scripts     ScriptOp (..),+    pattern OP_CHECKSIGADD,     Script (..),     PushDataType (..),     isPushOp,+    pushOpData,+    isMinimalPush,     opPushData,     intToScriptOp,     scriptOpToInt,@@ -38,8 +42,8 @@ import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial-import Data.Either (fromRight) import Data.Hashable+import Data.Maybe (isJust) import Data.Serialize (Serialize (..)) import Data.Word (Word8) import GHC.Generics (Generic)@@ -104,6 +108,12 @@   deriving (Show, Read, Eq, Generic, Hashable, NFData)  -- | Data type representing an operator allowed inside a 'Script'.+--+-- An 'OP_PUSHDATA' must carry a payload that fits its 'PushDataType': at most+-- 75 bytes for 'OPCODE', 255 for 'OPDATA1', 65535 for 'OPDATA2'. Serializing+-- a push that breaks this invariant fails with an 'error' call, because no+-- byte sequence can represent it. Build pushes with 'opPushData' to get the+-- minimal encoding. data ScriptOp   = -- Pushing Data     OP_PUSHDATA@@ -224,7 +234,8 @@   | OP_NOP8   | OP_NOP9   | OP_NOP10-  | -- Bitcoin Cash Nov 2018 hard fork+  | -- Bitcoin Cash Nov 2018 hard fork. Opcode 0xba is 'OP_CHECKSIGADD' in+    -- Tapscript (BIP 342), see the pattern synonym of that name.     OP_CHECKDATASIG   | OP_CHECKDATASIGVERIFY   | -- Bitcoin Cash May 2020 hard fork@@ -235,6 +246,13 @@   | OP_INVALIDOPCODE !Word8   deriving (Show, Read, Eq, Generic, Hashable, NFData) +-- | Opcode 0xba as Tapscript (BIP 342) names it. It is the same opcode as+-- 'OP_CHECKDATASIG' of Bitcoin Cash, so a value built with either name is+-- equal to the other and serializes to the same byte. 'Show' prints the+-- Bitcoin Cash name.+pattern OP_CHECKSIGADD :: ScriptOp+pattern OP_CHECKSIGADD = OP_CHECKDATASIG+ instance Serial ScriptOp where   deserialize = go . fromIntegral =<< getWord8     where@@ -398,7 +416,7 @@           putWord8 0x4d           putWord16le $ fromIntegral len         OPDATA4 -> do-          unless (len <= 0x7fffffff) $+          unless (len <= 0xffffffff) $             error "OP_PUSHDATA OPDATA4: Payload size too big"           putWord8 0x4e           putWord32le $ fromIntegral len@@ -539,33 +557,38 @@   put = serialize   get = deserialize --- | Check whether opcode is only data.+-- | Check whether opcode is only data: 'OP_PUSHDATA', 'OP_0', 'OP_1NEGATE' or+-- one of 'OP_1' to 'OP_16'. isPushOp :: ScriptOp -> Bool-isPushOp op = case op of-  OP_PUSHDATA _ _ -> True-  OP_0 -> True-  OP_1NEGATE -> True-  OP_1 -> True-  OP_2 -> True-  OP_3 -> True-  OP_4 -> True-  OP_5 -> True-  OP_6 -> True-  OP_7 -> True-  OP_8 -> True-  OP_9 -> True-  OP_10 -> True-  OP_11 -> True-  OP_12 -> True-  OP_13 -> True-  OP_14 -> True-  OP_15 -> True-  OP_16 -> True-  _ -> False+isPushOp = isJust . pushOpData --- | Optimally encode data using one of the 4 types of data pushing opcodes.+-- | The data that a push opcode puts on the stack. 'OP_0' pushes the empty+-- string, 'OP_1' to 'OP_16' push one byte with that value, 'OP_1NEGATE'+-- pushes the byte 0x81, and 'OP_PUSHDATA' pushes its payload. Other opcodes+-- push nothing and give 'Nothing'.+pushOpData :: ScriptOp -> Maybe ByteString+pushOpData op = case op of+  OP_PUSHDATA bs _ -> Just bs+  OP_0 -> Just B.empty+  OP_1NEGATE -> Just (B.singleton 0x81)+  _ -> B.singleton . fromIntegral <$> either (const Nothing) Just (scriptOpToInt op)++-- | Is the opcode a push in its shortest encoding? This is the rule of+-- @CheckMinimalPush@ in Bitcoin Core, which the @MINIMALDATA@ policy flag+-- enforces: the empty string is 'OP_0', one byte from 1 to 16 is the matching+-- 'OP_1' to 'OP_16', the byte 0x81 is 'OP_1NEGATE', and other data uses the+-- smallest 'PushDataType' that holds it. An opcode that pushes nothing is not+-- a minimal push.+isMinimalPush :: ScriptOp -> Bool+isMinimalPush op = maybe False (\bs -> opPushData bs == op) (pushOpData op)++-- | Encode data as the push opcode of its minimal encoding, see+-- 'isMinimalPush'. opPushData :: ByteString -> ScriptOp opPushData bs+  | len == 0 = OP_0+  | len == 1, Right op <- intToScriptOp (fromIntegral (B.head bs)) = op+  | bs == B.singleton 0x81 = OP_1NEGATE   | len <= 0x4b = OP_PUSHDATA bs OPCODE   | len <= 0xff = OP_PUSHDATA bs OPDATA1   | len <= 0xffff = OP_PUSHDATA bs OPDATA2@@ -574,25 +597,46 @@   where     len = B.length bs --- | Transforms integers @[1 .. 16]@ to 'ScriptOp' @[OP_1 .. OP_16]@.-intToScriptOp :: Int -> ScriptOp-intToScriptOp i-  | i `elem` [1 .. 16] = op-  | otherwise = err-  where-    op =-      fromRight err-        . runGetS deserialize-        . B.singleton-        . fromIntegral-        $ i + 0x50-    err = error $ "intToScriptOp: Invalid integer " ++ show i+-- | Transforms integers @[1 .. 16]@ to 'ScriptOp' @[OP_1 .. OP_16]@. Other+-- integers give a 'Left'.+intToScriptOp :: Int -> Either String ScriptOp+intToScriptOp i = case i of+  1 -> Right OP_1+  2 -> Right OP_2+  3 -> Right OP_3+  4 -> Right OP_4+  5 -> Right OP_5+  6 -> Right OP_6+  7 -> Right OP_7+  8 -> Right OP_8+  9 -> Right OP_9+  10 -> Right OP_10+  11 -> Right OP_11+  12 -> Right OP_12+  13 -> Right OP_13+  14 -> Right OP_14+  15 -> Right OP_15+  16 -> Right OP_16+  _ -> Left $ "intToScriptOp: invalid integer " ++ show i --- | Decode 'ScriptOp' @[OP_1 .. OP_16]@ to integers @[1 .. 16]@. This functions--- fails for other values of 'ScriptOp'+-- | Decode 'ScriptOp' @[OP_1 .. OP_16]@ to integers @[1 .. 16]@. Other+-- opcodes give a 'Left'. scriptOpToInt :: ScriptOp -> Either String Int-scriptOpToInt s-  | res `elem` [1 .. 16] = return res-  | otherwise = Left $ "scriptOpToInt: invalid opcode " ++ show s-  where-    res = fromIntegral (B.head $ runPutS $ serialize s) - 0x50+scriptOpToInt op = case op of+  OP_1 -> Right 1+  OP_2 -> Right 2+  OP_3 -> Right 3+  OP_4 -> Right 4+  OP_5 -> Right 5+  OP_6 -> Right 6+  OP_7 -> Right 7+  OP_8 -> Right 8+  OP_9 -> Right 9+  OP_10 -> Right 10+  OP_11 -> Right 11+  OP_12 -> Right 12+  OP_13 -> Right 13+  OP_14 -> Right 14+  OP_15 -> Right 15+  OP_16 -> Right 16+  _ -> Left $ "scriptOpToInt: invalid opcode " ++ show op
src/Haskoin/Script/SigHash.hs view
@@ -27,6 +27,7 @@     sigHashAll,     sigHashNone,     sigHashSingle,+    sigHashDefault,     anyoneCanPay,     hasForkIdFlag,     setAnyoneCanPay,@@ -126,6 +127,12 @@   toJSON = Number . fromIntegral   toEncoding (SigHash n) = toEncoding n +-- | SIGHASH_DEFAULT of BIP 341: the hash type 0, which a Taproot key path+-- signature leaves out and which signs like 'sigHashAll'. It is only valid+-- in Taproot signatures.+sigHashDefault :: SigHash+sigHashDefault = 0+ -- | SIGHASH_NONE as a byte. sigHashNone :: SigHash sigHashNone = fromIntegral $ fromEnum SIGHASH_NONE@@ -174,10 +181,17 @@ isSigHashSingle :: SigHash -> Bool isSigHashSingle = (== sigHashSingle) . (.&. 0x1f) --- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_UNKNOWN'.+-- | Is the hash type byte one that no node accepts in a signature? This is+-- the test of @IsDefinedHashtypeSignature@ in Bitcoin Core, which the+-- @STRICTENC@ flag enforces: after masking the 'SIGHASH_ANYONECANPAY' and+-- 'SIGHASH_FORKID' bits, the byte must be 'SIGHASH_ALL', 'SIGHASH_NONE' or+-- 'SIGHASH_SINGLE'. Whether the 'SIGHASH_FORKID' bit itself is allowed+-- depends on the network, see 'decodeTxSig'. Only the low byte is looked at,+-- since a fork id may sit in the upper bytes. isSigHashUnknown :: SigHash -> Bool-isSigHashUnknown =-  (`notElem` [sigHashAll, sigHashNone, sigHashSingle]) . (.&. 0x1f)+isSigHashUnknown sh = base < sigHashAll || base > sigHashSingle+  where+    base = (sh .&. 0xff) .&. complement (sigHashAnyoneCanPay .|. sigHashForkId)  -- | Add a fork id to a 'SigHash'. sigHashAddForkId :: SigHash -> Word32 -> SigHash@@ -192,7 +206,23 @@ sigHashGetForkId :: SigHash -> Word32 sigHashGetForkId (SigHash n) = fromIntegral $ n `shiftR` 8 --- | Computes the hash that will be used for signing a transaction.+-- | Computes the hash that will be used for signing a transaction input that+-- is not a SegWit input.+--+-- The script is the @scriptCode@ of the input: the output script being spent,+-- or the redeem script for a pay-to-script-hash output. If that script+-- contains 'OP_CODESEPARATOR', the caller must pass the part after the last+-- 'OP_CODESEPARATOR' executed before the signature check, as consensus+-- defines it. Any 'OP_CODESEPARATOR' left in that part is removed here, as+-- @FindAndDelete@ does in Bitcoin Core.+--+-- On a network with a fork id, such as Bitcoin Cash, every signature must use+-- the fork id digest of 'txSigHashForkId', so the 'SigHash' must carry the+-- 'SIGHASH_FORKID' flag and the digest is computed by that function. A+-- 'SigHash' without the flag fails, instead of producing a digest no node+-- accepts.+--+-- Fails when the input index is not an index of the transaction. txSigHash ::   Network ->   -- | transaction to sign@@ -206,14 +236,18 @@   -- | what to sign   SigHash ->   -- | hash to be signed-  Hash256+  Either String Hash256 txSigHash net tx out v i sh-  | hasForkIdFlag sh && isJust net.sigHashForkId =+  | i < 0 || i >= length tx.inputs =+      Left "txSigHash: input index out of range"+  | isJust net.sigHashForkId && not (hasForkIdFlag sh) =+      Left "txSigHash: SIGHASH_FORKID is required on this network"+  | isJust net.sigHashForkId =       txSigHashForkId net tx out v i sh   | otherwise = do       let newIn = buildInputs tx.inputs fout i sh       -- When SigSingle and input index > outputs, then sign integer 1-      fromMaybe one $ do+      return . fromMaybe one $ do         newOut <- buildOutputs tx.outputs i sh         let newTx = Tx tx.version newIn newOut [] tx.locktime         return . doubleSHA256 . runPutS $ do@@ -223,12 +257,16 @@     fout = Script $ filter (/= OP_CODESEPARATOR) out.ops     one = "0100000000000000000000000000000000000000000000000000000000000000" --- | Build transaction inputs for computing sighashes.+-- | Build transaction inputs for computing sighashes. With+-- 'SIGHASH_ANYONECANPAY' only the signed input remains. With 'SIGHASH_NONE'+-- or 'SIGHASH_SINGLE' the sequence numbers of the other inputs are zero. Any+-- other base type, including the undefined ones, behaves as 'SIGHASH_ALL',+-- as in Bitcoin Core. buildInputs :: [TxIn] -> Script -> Int -> SigHash -> [TxIn] buildInputs txins out i sh   | anyoneCanPay sh = [serialOut (txins !! i)]-  | isSigHashAll sh || isSigHashUnknown sh = single-  | otherwise = zipWith noSeq single [0 ..]+  | isSigHashNone sh || isSigHashSingle sh = zipWith noSeq single [0 ..]+  | otherwise = single   where     serialOut TxIn {..} = TxIn {script = runPutS $ serialize out, ..}     emptyIn TxIn {..} = TxIn {script = B.empty, ..}@@ -236,18 +274,31 @@     single = updateIndex i emptyIns serialOut     noSeq TxIn {..} j = TxIn {sequence = if i == j then sequence else 0, ..} --- | Build transaction outputs for computing sighashes.+-- | Build transaction outputs for computing sighashes. 'SIGHASH_NONE' signs+-- no output. 'SIGHASH_SINGLE' signs the output at the input index, with the+-- outputs before it blanked, and gives 'Nothing' when there is no such+-- output. Any other base type signs every output. buildOutputs :: [TxOut] -> Int -> SigHash -> Maybe [TxOut] buildOutputs txos i sh-  | isSigHashAll sh || isSigHashUnknown sh = return txos   | isSigHashNone sh = return []+  | not (isSigHashSingle sh) = return txos   | i >= length txos = Nothing   | otherwise = return $ buffer ++ [txos !! i]   where     buffer = replicate i $ TxOut maxBound B.empty --- | Compute the hash that will be used for signing a transaction. This--- function is used when the 'SIGHASH_FORKID' flag is set.+-- | Compute the digest of BIP 143, used for SegWit version 0 inputs on+-- Bitcoin and, with the fork id of the network, for every input on Bitcoin+-- Cash.+--+-- The script is the @scriptCode@ of BIP 143: the pay-to-public-key-hash+-- script of the key for a pay-to-witness-public-key-hash input, or the+-- witness script for a pay-to-witness-script-hash input. If it contains+-- 'OP_CODESEPARATOR', the caller must pass the part after the last one+-- executed before the signature check. Unlike 'txSigHash', nothing is removed+-- from that part, as BIP 143 specifies.+--+-- Fails when the input index is not an index of the transaction. txSigHashForkId ::   Network ->   -- | transaction to sign@@ -261,9 +312,11 @@   -- | what to sign   SigHash ->   -- | hash to be signed-  Hash256-txSigHashForkId net tx out v i sh =-  doubleSHA256 . runPutS $ do+  Either String Hash256+txSigHashForkId net tx out v i sh+  | i < 0 || i >= length tx.inputs =+      Left "txSigHashForkId: input index out of range"+  | otherwise = Right . doubleSHA256 . runPutS $ do     putWord32le tx.version     serialize hashPrevouts     serialize hashSequence@@ -299,6 +352,14 @@ -- | Data type representing a signature together with a 'SigHash'. The 'SigHash' -- is serialized as one byte at the end of an ECDSA 'Sig'. All signatures in -- transaction inputs are of type 'TxSignature'.+--+-- The binary form is as strict as the @DERSIG@, @LOW_S@ and @STRICTENC@ flags+-- of Bitcoin Core: strict DER, a low S value, non-zero components, a defined+-- hash type byte ('isSigHashUnknown'), the 'SIGHASH_FORKID' flag exactly on+-- the networks that have a fork id, and nothing after the hash type byte.+-- These are the policy rules every node applies to new transactions. Inputs+-- mined before BIP 66 and the low-S rule may hold signatures that this form+-- rejects, so it is not a parser for the whole chain history. data TxSignature   = TxSignature       { sig :: !Sig,@@ -324,6 +385,8 @@           fail "Non-canonical signature: unknown hashtype byte"         when (isNothing net.sigHashForkId && hasForkIdFlag sh) $           fail "Non-canonical signature: invalid network for forkId"+        when (isJust net.sigHashForkId && not (hasForkIdFlag sh)) $+          fail "Non-canonical signature: SIGHASH_FORKID is required on this network"         return $ TxSignature sig sh  instance MarshalJSON (Network, Ctx) TxSignature where@@ -337,9 +400,13 @@           Left e -> fail e           Right s -> return s +-- | Serialize a signature with its hash type byte. 'TxSignatureEmpty' is the+-- empty string. encodeTxSig :: Network -> Ctx -> TxSignature -> ByteString encodeTxSig net ctx = runPutS . marshalPut (net, ctx) +-- | Parse a signature with its hash type byte, under the rules listed at+-- 'TxSignature'. The empty string is 'TxSignatureEmpty'. decodeTxSig :: Network -> Ctx -> ByteString -> Either String TxSignature decodeTxSig net ctx =   runGetS $ do
src/Haskoin/Script/Standard.hs view
@@ -32,6 +32,7 @@     isPayWitnessScriptHash,     isPayScript32Hash,     isDataCarrier,+    maxMultisigKeys,     encodeOutput,     decodeOutput,     toP2SH,@@ -47,12 +48,13 @@     isSpendPKHash,     isSpendMulSig,     isScriptHashInput,+    isNestedWitnessInput,   ) where  import Control.Applicative ((<|>)) import Control.DeepSeq-import Control.Monad (guard, liftM2, (<=<))+import Control.Monad (unless) import Crypto.Secp256k1 import Data.Aeson (ToJSON (..), Value (..), withText) import Data.Aeson.Encoding (Encoding, text)@@ -65,7 +67,6 @@ import Data.Function (on) import Data.Hashable import Data.List (sortBy)-import Data.Maybe (fromJust, isJust) import Data.Word (Word8) import GHC.Generics (Generic) import Haskoin.Crypto.Hash@@ -83,7 +84,7 @@     PayPK {key :: !PublicKey}   | -- | pay to public key hash     PayPKHash {hash160 :: !Hash160}-  | -- | multisig+  | -- | multisig of 1 to 'maxMultisigKeys' keys     PayMulSig       { keys :: ![PublicKey],         required :: !Int@@ -94,15 +95,16 @@     PayWitnessPKHash {hash160 :: !Hash160}   | -- | pay to witness script hash     PayWitnessScriptHash {hash256 :: !Hash256}-  | -- | another pay to witness address+  | -- | witness program of version 1 to 16, from 2 to 40 bytes long     PayWitness       { version :: !Word8,         bytes :: !ByteString       }   | -- | pay to a script hash (256-bit)     PayScript32Hash {hash256 :: !Hash256}-  | -- | provably unspendable data carrier-    DataCarrier {bytes :: !ByteString}+  | -- | provably unspendable data carrier: @OP_RETURN@ followed by the data+    -- of each push, which may be no push at all+    DataCarrier {pushes :: ![ByteString]}   deriving (Eq, Show, Read, Generic, NFData)  instance MarshalJSON Ctx ScriptOutput where@@ -161,140 +163,143 @@ isDataCarrier (DataCarrier _) = True isDataCarrier _ = False +-- | The most keys a multisig output can have: @MAX_PUBKEYS_PER_MULTISIG@ of+-- Bitcoin Core.+maxMultisigKeys :: Int+maxMultisigKeys = 20+ -- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the -- script is not recognized as any of the standard output types.+--+-- The templates are matched on their exact bytes, as Bitcoin Core matches+-- them: the hash or key of a pay-to-public-key, pay-to-public-key-hash,+-- pay-to-script-hash or witness output must be a direct push ('OPCODE'). A+-- script that writes it with @OP_PUSHDATA1@ is not that output type. By+-- consensus it is not a pay-to-script-hash or witness output at all, and+-- anyone can spend it, so it must not be shown as the address it looks like.+-- The counts of a multisig output are @OP_1@ to @OP_16@, or a one-byte push+-- for 17 to 'maxMultisigKeys', and a key must fill its push exactly. decodeOutput :: Ctx -> Script -> Either String ScriptOutput decodeOutput ctx s = case s.ops of   -- Pay to PubKey-  [OP_PUSHDATA bs _, OP_CHECKSIG]-    | B.length bs == 33 || B.length bs == 65 ->-        PayPK <$> unmarshal ctx bs-    | otherwise ->-        Left "decodeOutput: public key must be 33 or 65 bytes long"+  [OP_PUSHDATA bs OPCODE, OP_CHECKSIG] ->+    PayPK <$> decodePubKey ctx bs   -- Pay to PubKey Hash-  [OP_DUP, OP_HASH160, OP_PUSHDATA bs _, OP_EQUALVERIFY, OP_CHECKSIG]+  [OP_DUP, OP_HASH160, OP_PUSHDATA bs OPCODE, OP_EQUALVERIFY, OP_CHECKSIG]     | B.length bs == 20 ->         PayPKHash <$> runGetS deserialize bs     | otherwise ->         Left "decodeOutput: public key hash must be 20 bytes long"   -- Pay to Script Hash-  [OP_HASH160, OP_PUSHDATA bs _, OP_EQUAL]+  [OP_HASH160, OP_PUSHDATA bs OPCODE, OP_EQUAL]     | B.length bs == 20 ->         PayScriptHash <$> runGetS deserialize bs     | otherwise ->         Left "decodeOutput: script hash must be 20 bytes long"   -- Pay to 256-bit Script Hash-  [OP_HASH256, OP_PUSHDATA bs _, OP_EQUAL]+  [OP_HASH256, OP_PUSHDATA bs OPCODE, OP_EQUAL]     | B.length bs == 32 ->         PayScript32Hash <$> runGetS deserialize bs     | otherwise ->         Left "decodeOutput: script hash must be 32 bytes long"-  -- Pay to Witness-  [OP_0, OP_PUSHDATA bs OPCODE]-    | B.length bs == 20 ->-        PayWitnessPKHash <$> runGetS deserialize bs-    | B.length bs == 32 ->-        PayWitnessScriptHash <$> runGetS deserialize bs-    | otherwise ->-        Left "decodeOutput: version 0 segwit must be 20 or 32 bytes long"-  -- Other Witness-  [ver, OP_PUSHDATA bs _]+  -- Witness programs+  [ver, OP_PUSHDATA bs OPCODE]+    | Just 0 <- opWitnessVersion ver -> case B.length bs of+        20 -> PayWitnessPKHash <$> runGetS deserialize bs+        32 -> PayWitnessScriptHash <$> runGetS deserialize bs+        _ -> Left "decodeOutput: version 0 segwit must be 20 or 32 bytes long"     | Just wv <- opWitnessVersion ver,-      B.length bs >= 2,-      B.length bs <= 40 ->+      B.length bs >= minWitnessProgramLength,+      B.length bs <= maxWitnessProgramLength ->         Right $ PayWitness wv bs   -- Provably unspendable data carrier output-  [OP_RETURN, OP_PUSHDATA bs _] -> Right $ DataCarrier bs+  OP_RETURN : rest+    | Just ds <- mapM pushOpData rest -> Right $ DataCarrier ds   -- Pay to MultiSig Keys   _ -> case matchPayMulSig ctx s of     Right x -> return x     Left _ -> Left "decodeOutput: Non-standard output" +-- | Shortest and longest witness program, in bytes, as BIP 141 defines them.+minWitnessProgramLength, maxWitnessProgramLength :: Int+minWitnessProgramLength = 2+maxWitnessProgramLength = 40++-- | The opcode that starts a witness program of a version. witnessVersionOp :: Word8 -> Maybe ScriptOp witnessVersionOp 0 = Just OP_0-witnessVersionOp 1 = Just OP_1-witnessVersionOp 2 = Just OP_2-witnessVersionOp 3 = Just OP_3-witnessVersionOp 4 = Just OP_4-witnessVersionOp 5 = Just OP_5-witnessVersionOp 6 = Just OP_6-witnessVersionOp 7 = Just OP_7-witnessVersionOp 8 = Just OP_8-witnessVersionOp 9 = Just OP_9-witnessVersionOp 10 = Just OP_10-witnessVersionOp 11 = Just OP_11-witnessVersionOp 12 = Just OP_12-witnessVersionOp 13 = Just OP_13-witnessVersionOp 14 = Just OP_14-witnessVersionOp 15 = Just OP_15-witnessVersionOp 16 = Just OP_16-witnessVersionOp _ = Nothing+witnessVersionOp v = eitherToMaybe $ intToScriptOp $ fromIntegral v +-- | The witness version that an opcode starts. opWitnessVersion :: ScriptOp -> Maybe Word8 opWitnessVersion OP_0 = Just 0-opWitnessVersion OP_1 = Just 1-opWitnessVersion OP_2 = Just 2-opWitnessVersion OP_3 = Just 3-opWitnessVersion OP_4 = Just 4-opWitnessVersion OP_5 = Just 5-opWitnessVersion OP_6 = Just 6-opWitnessVersion OP_7 = Just 7-opWitnessVersion OP_8 = Just 8-opWitnessVersion OP_9 = Just 9-opWitnessVersion OP_10 = Just 10-opWitnessVersion OP_11 = Just 11-opWitnessVersion OP_12 = Just 12-opWitnessVersion OP_13 = Just 13-opWitnessVersion OP_14 = Just 14-opWitnessVersion OP_15 = Just 15-opWitnessVersion OP_16 = Just 16-opWitnessVersion _ = Nothing+opWitnessVersion op = fromIntegral <$> eitherToMaybe (scriptOpToInt op) --- | Computes a 'Script' from a standard 'ScriptOutput'.-encodeOutput :: Ctx -> ScriptOutput -> Script-encodeOutput ctx s = Script $ case s of+-- | Computes a 'Script' from a standard 'ScriptOutput'. Fails for a value+-- that no script can express: a 'PayMulSig' whose required count is not+-- between 1 and its number of keys, or with more than 'maxMultisigKeys' keys,+-- and a 'PayWitness' whose version is not 1 to 16 or whose program is not 2+-- to 40 bytes long. A version 0 program is a 'PayWitnessPKHash' or a+-- 'PayWitnessScriptHash'.+encodeOutput :: Ctx -> ScriptOutput -> Either String Script+encodeOutput ctx s = Script <$> case s of   -- Pay to PubKey-  (PayPK k) -> [opPushData $ marshal ctx k, OP_CHECKSIG]+  (PayPK k) -> Right [opPushData $ marshal ctx k, OP_CHECKSIG]   -- Pay to PubKey Hash Address   (PayPKHash h) ->-    [ OP_DUP,-      OP_HASH160,-      opPushData $ runPutS $ serialize h,-      OP_EQUALVERIFY,-      OP_CHECKSIG-    ]+    Right+      [ OP_DUP,+        OP_HASH160,+        opPushData $ runPutS $ serialize h,+        OP_EQUALVERIFY,+        OP_CHECKSIG+      ]   -- Pay to MultiSig Keys   (PayMulSig ps r)-    | r <= length ps ->-        let opM = intToScriptOp r-            opN = intToScriptOp $ length ps-            keys = map (opPushData . marshal ctx) ps-         in opM : keys ++ [opN, OP_CHECKMULTISIG]-    | otherwise -> error "encodeOutput: PayMulSig r must be <= than pkeys"+    | r < 1 ->+        Left "encodeOutput: multisig must require at least one signature"+    | r > n ->+        Left "encodeOutput: multisig requires more signatures than it has keys"+    | n > maxMultisigKeys ->+        Left "encodeOutput: multisig has more than 20 keys"+    | otherwise ->+        let keys = map (opPushData . marshal ctx) ps+         in Right $ multisigCountOp r : keys ++ [multisigCountOp n, OP_CHECKMULTISIG]+    where+      n = length ps   -- Pay to Script Hash Address   (PayScriptHash h) ->-    [OP_HASH160, opPushData $ runPutS $ serialize h, OP_EQUAL]+    Right [OP_HASH160, opPushData $ runPutS $ serialize h, OP_EQUAL]+  -- Pay to 256-bit Script Hash Address+  (PayScript32Hash h) ->+    Right [OP_HASH256, opPushData $ runPutS $ serialize h, OP_EQUAL]   -- Pay to Witness PubKey Hash Address   (PayWitnessPKHash h) ->-    [OP_0, opPushData $ runPutS $ serialize h]+    Right [OP_0, opPushData $ runPutS $ serialize h]   (PayWitnessScriptHash h) ->-    [OP_0, opPushData $ runPutS $ serialize h]-  (PayWitness v h) ->-    [ case witnessVersionOp v of-        Nothing -> error "encodeOutput: invalid witness version"-        Just c -> c,-      opPushData h-    ]+    Right [OP_0, opPushData $ runPutS $ serialize h]+  (PayWitness v h)+    | v == 0 ->+        Left "encodeOutput: a version 0 witness output is PayWitnessPKHash or PayWitnessScriptHash"+    | B.length h < minWitnessProgramLength || B.length h > maxWitnessProgramLength ->+        Left "encodeOutput: witness program must be 2 to 40 bytes long"+    | Just c <- witnessVersionOp v ->+        Right [c, opPushData h]+    | otherwise ->+        Left "encodeOutput: witness version must be 16 or less"   -- Provably unspendable output-  (DataCarrier d) -> [OP_RETURN, opPushData d]+  (DataCarrier ds) -> Right $ OP_RETURN : map opPushData ds +-- | The 'Script' of a 'ScriptOutput' is the binary form. Writing a value that+-- 'encodeOutput' rejects fails with an 'error' call, since 'marshalPut'+-- cannot report a failure. instance Marshal Ctx ScriptOutput where   marshalGet ctx = do     script <- deserialize     case decodeOutput ctx script of       Left e -> fail e       Right o -> return o-  marshalPut ctx = serialize . encodeOutput ctx+  marshalPut ctx = either error serialize . encodeOutput ctx  -- | Encode script as pay-to-script-hash script toP2SH :: Script -> ScriptOutput@@ -304,35 +309,50 @@ toP2WSH :: Script -> ScriptOutput toP2WSH = PayWitnessScriptHash . sha256 . runPutS . serialize --- | Match @[OP_N, PubKey1, ..., PubKeyM, OP_M, OP_CHECKMULTISIG]@+-- | Match @[OP_M, PubKey1, ..., PubKeyN, OP_N, OP_CHECKMULTISIG]@ with M and N+-- from 1 to 'maxMultisigKeys' and M at most N. matchPayMulSig :: Ctx -> Script -> Either String ScriptOutput matchPayMulSig ctx (Script ops) = case splitAt (length ops - 2) ops of   (m : xs, [n, OP_CHECKMULTISIG]) -> do-    (intM, intN) <- liftM2 (,) (scriptOpToInt m) (scriptOpToInt n)-    if intM <= intN && length xs == intN-      then liftM2 PayMulSig (go xs) (return intM)-      else Left "matchPayMulSig: Invalid M or N parameters"+    intM <- maybeToEither "matchPayMulSig: invalid M parameter" $ opMultisigCount m+    intN <- maybeToEither "matchPayMulSig: invalid N parameter" $ opMultisigCount n+    unless (intM <= intN && length xs == intN) $+      Left "matchPayMulSig: Invalid M or N parameters"+    keys <- mapM key xs+    return $ PayMulSig keys intM   _ -> Left "matchPayMulSig: script did not match output template"   where-    go (OP_PUSHDATA bs _ : xs)-      | B.length bs == 33 || B.length bs == 65 =-          liftM2 (:) (unmarshal ctx bs) (go xs)-      | otherwise =-          Left "matchPayMulSig: public key must be 33 or 65 bytes long"-    go [] =-      Right []-    go _ =-      Left "matchPayMulSig: invalid multisig opcode"+    key (OP_PUSHDATA bs _) = decodePubKey ctx bs+    key _ = Left "matchPayMulSig: invalid multisig opcode" +-- | The count that a multisig opcode stands for: @OP_1@ to @OP_16@, or a+-- one-byte push of 17 to 'maxMultisigKeys'. Bitcoin Core reads the count as a+-- minimal script number, so a push of a byte from 1 to 16 is not a count.+opMultisigCount :: ScriptOp -> Maybe Int+opMultisigCount op = case op of+  OP_PUSHDATA bs OPCODE+    | B.length bs == 1,+      let n = fromIntegral (B.head bs),+      n > 16 && n <= maxMultisigKeys ->+        Just n+  _ -> eitherToMaybe $ scriptOpToInt op++-- | The opcode of a multisig count from 1 to 'maxMultisigKeys'.+multisigCountOp :: Int -> ScriptOp+multisigCountOp n =+  either (const $ OP_PUSHDATA (B.singleton $ fromIntegral n) OPCODE) id $+    intToScriptOp n+ -- | Sort the public keys of a multisig output in ascending order by comparing--- their compressed serialized representations. Refer to BIP-67.+-- their serialized representations. Refer to BIP-67, which defines the order+-- for compressed keys. Any other output is returned unchanged. sortMulSig :: Ctx -> ScriptOutput -> ScriptOutput sortMulSig ctx out = case out of   PayMulSig keys r ->     PayMulSig       (sortBy (compare `on` marshal ctx) keys)       r-  _ -> error "Can only call orderMulSig on PayMulSig scripts"+  _ -> out  -- | Data type describing standard transaction input scripts. Input scripts -- provide the signing data required to unlock the coins of the output they are@@ -377,6 +397,12 @@ isScriptHashInput (ScriptHashInput _ _) = True isScriptHashInput _ = False +-- | Returns true if the input script is spending a pay-to-script-hash output+-- that wraps a SegWit program.+isNestedWitnessInput :: ScriptInput -> Bool+isNestedWitnessInput (NestedWitnessInput _) = True+isNestedWitnessInput _ = False+ -- | A redeem script is the output script serialized into the spending input -- script. It must be included in inputs that spend pay-to-script-hash outputs. type RedeemScript = ScriptOutput@@ -393,9 +419,28 @@         -- | redeem script         redeem :: !RedeemScript       }+  | -- | input of a pay-to-script-hash output that wraps a SegWit program+    -- (P2SH-P2WPKH or P2SH-P2WSH): the script only pushes the witness output+    -- script, and the signatures are in the witness+    NestedWitnessInput+      { -- | the wrapped 'PayWitnessPKHash' or 'PayWitnessScriptHash' output+        redeem :: !RedeemScript+      }   deriving (Show, Read, Eq, Generic, NFData) --- | Heuristic to decode an input script into one of the standard types.+-- | The signature that a script element stands for: an empty push is+-- 'TxSignatureEmpty', any other push must decode as a signature.+sigOp :: Network -> Ctx -> ScriptOp -> Maybe TxSignature+sigOp net ctx op = do+  bs <- pushOpData op+  if B.null bs+    then return TxSignatureEmpty+    else eitherToMaybe $ decodeTxSig net ctx bs++-- | Heuristic to decode an input script into one of the standard types, when+-- the output it spends is not known. A single empty push is read as a+-- 'SpendPK' with an empty signature, although it is also the form of a+-- 'SpendMulSig' without signatures. decodeSimpleInput :: Network -> Ctx -> Script -> Either String SimpleInput decodeSimpleInput net ctx (Script ops) =   maybeToEither errMsg $ matchPK ops <|> matchPKHash ops <|> matchMulSig ops@@ -403,20 +448,36 @@     matchPK [op] = SpendPK <$> f op     matchPK _ = Nothing     matchPKHash [op, OP_PUSHDATA pub _] =-      SpendPKHash <$> f op <*> eitherToMaybe (unmarshal ctx pub)+      SpendPKHash <$> f op <*> eitherToMaybe (decodePubKey ctx pub)     matchPKHash _ = Nothing-    matchMulSig (x : xs) = do-      guard $ x == OP_0-      SpendMulSig <$> mapM f xs+    matchMulSig (OP_0 : xs) = SpendMulSig <$> mapM f xs     matchMulSig _ = Nothing-    f OP_0 = return TxSignatureEmpty-    f (OP_PUSHDATA "" OPCODE) = f OP_0-    f (OP_PUSHDATA bs _) = eitherToMaybe $ decodeTxSig net ctx bs-    f _ = Nothing+    f = sigOp net ctx     errMsg = "decodeInput: Could not decode script input" +-- | Decode the input of a pay-to-script-hash output as the redeem script+-- requires: one signature for 'PayPK', a signature and a key for+-- 'PayPKHash', and @OP_0@ followed by the signatures, possibly none, for+-- 'PayMulSig'. Other redeem scripts have no standard input.+decodeSimpleInputFor ::+  Network -> Ctx -> RedeemScript -> [ScriptOp] -> Maybe SimpleInput+decodeSimpleInputFor net ctx rdm ops = case (rdm, ops) of+  (PayPK _, [op]) -> SpendPK <$> f op+  (PayPKHash _, [op, OP_PUSHDATA pub _]) ->+    SpendPKHash <$> f op <*> eitherToMaybe (decodePubKey ctx pub)+  (PayMulSig _ _, OP_0 : xs) -> SpendMulSig <$> mapM f xs+  _ -> Nothing+  where+    f = sigOp net ctx+ -- | Heuristic to decode a 'ScriptInput' from a 'Script'. This function fails if -- the script can not be parsed as a standard script input.+--+-- A script whose last element is a standard output script is the input of a+-- pay-to-script-hash output. The elements before it must be the input that+-- the redeem script requires, see 'ScriptHashInput'. When the redeem script+-- is a SegWit program and nothing comes before it, the input is a+-- 'NestedWitnessInput'. decodeInput :: Network -> Ctx -> Script -> Either String ScriptInput decodeInput net ctx s@(Script ops) =   maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash@@ -427,8 +488,12 @@       case splitAt (length s.ops - 1) ops of         (is, [OP_PUSHDATA bs _]) -> do           rdm <- eitherToMaybe $ unmarshal ctx bs-          inp <- eitherToMaybe $ decodeSimpleInput net ctx $ Script is-          return $ ScriptHashInput inp rdm+          case (is, rdm) of+            ([], PayWitnessPKHash _) -> return $ NestedWitnessInput rdm+            ([], PayWitnessScriptHash _) -> return $ NestedWitnessInput rdm+            _ -> do+              inp <- decodeSimpleInputFor net ctx rdm is+              return $ ScriptHashInput inp rdm         _ -> Nothing     errMsg = "decodeInput: Could not decode script input" @@ -445,6 +510,8 @@   RegularInput ri -> encodeSimpleInput net ctx ri   ScriptHashInput i o ->     Script $ (encodeSimpleInput net ctx i).ops ++ [opPushData $ marshal ctx o]+  NestedWitnessInput o ->+    Script [opPushData $ marshal ctx o]  -- | Encode a standard 'SimpleInput' into opcodes as an input 'Script'. encodeSimpleInput :: Network -> Ctx -> SimpleInput -> Script
src/Haskoin/Transaction.hs view
@@ -14,7 +14,7 @@     module Builder,     module Segwit,     module Taproot,-    module Partial,+    module Psbt,     module Genesis,   ) where@@ -22,6 +22,6 @@ import Haskoin.Transaction.Builder as Builder import Haskoin.Transaction.Common as Common import Haskoin.Transaction.Genesis as Genesis-import Haskoin.Transaction.Partial as Partial+import Haskoin.Transaction.Psbt as Psbt import Haskoin.Transaction.Segwit as Segwit import Haskoin.Transaction.Taproot as Taproot
src/Haskoin/Transaction/Builder.hs view
@@ -28,6 +28,7 @@     makeSignature,     signInput,     signNestedInput,+    signTaprootInput,     verifyStdTx,     mergeTxs,     sigKeys,@@ -39,31 +40,29 @@     Coin (..),     chooseCoins,     chooseCoinsSink,-    chooseMSCoins,-    chooseMSCoinsSink,     countMulSig,     greedyAddSink,-    guessTxFee,-    guessMSTxFee,++    -- * Size and fee estimation+    InputKind (..),+    OutputKind (..),+    guessTxWeight,     guessTxSize,-    guessMSSize,+    guessTxFee,+    inputWeight,+    outputSize,   ) where -import Control.Applicative ((<|>))-import Control.Arrow (first) import Control.Monad (foldM, unless) import Control.Monad.Identity (runIdentity) import Crypto.Secp256k1 import Data.ByteString qualified as B-import Data.Bytes.Get-import Data.Bytes.Put-import Data.Bytes.Serial import Data.Conduit (ConduitT, Void, await, runConduit, (.|)) import Data.Conduit.List (sourceList) import Data.Either (fromRight, rights)-import Data.List (nub)-import Data.Maybe (catMaybes, fromJust, isJust)+import Data.List (find, nub)+import Data.Maybe (catMaybes, fromJust, isJust, mapMaybe) import Data.String.Conversions (cs) import Data.Text (Text) import Data.Word (Word64)@@ -71,114 +70,68 @@ import Haskoin.Crypto.Hash (Hash256, addressHash) import Haskoin.Crypto.Keys.Common import Haskoin.Crypto.Signature-import Haskoin.Network.Common import Haskoin.Network.Data import Haskoin.Script-import Haskoin.Transaction.Builder.Sign (SigInput, buildInput, makeSignature, sigKeys)+import Haskoin.Transaction.Builder.Sign (SigInput, buildInput, makeSignature, sigKeys, signTaprootInput) import Haskoin.Transaction.Builder.Sign qualified as Sign import Haskoin.Transaction.Common import Haskoin.Transaction.Segwit+import Haskoin.Transaction.Taproot import Haskoin.Util --- | Any type can be used as a Coin if it can provide a value in Satoshi.--- The value is used in coin selection algorithms.+-- | Any type can be used as a Coin if it can provide a value in Satoshi and+-- say how it is spent. The value and the kind of input are used in coin+-- selection algorithms, to reach the amount and to estimate the fee. class Coin c where   coinValue :: c -> Word64+  coinInputKind :: c -> InputKind --- | Coin selection algorithm for normal (non-multisig) transactions. This--- function returns the selected coins together with the amount of change to--- send back to yourself, taking the fee into account.+-- | Coin selection algorithm. This function returns the selected coins+-- together with the amount of change to send back to yourself, taking the+-- fee into account. The fee is estimated from the kind of input each coin+-- is spent with and the kinds of the outputs, with 'guessTxFee'. chooseCoins ::   (Coin c) =>   -- | value to send   Word64 ->-  -- | fee per byte+  -- | fee per virtual byte   Word64 ->-  -- | number of outputs (including change)-  Int ->+  -- | outputs of the transaction, including the change+  [OutputKind] ->   -- | try to find better solutions   Bool ->   -- | list of ordered coins to choose from   [c] ->   -- | coin selection and change   Either String ([c], Word64)-chooseCoins target fee nOut continue coins =+chooseCoins target fee outputs continue coins =   runIdentity . runConduit $-    sourceList coins .| chooseCoinsSink target fee nOut continue+    sourceList coins .| chooseCoinsSink target fee outputs continue --- | Coin selection algorithm for normal (non-multisig) transactions. This--- function returns the selected coins together with the amount of change to--- send back to yourself, taking the fee into account. This version uses a Sink--- for conduit-based coin selection.+-- | Coin selection algorithm. This function returns the selected coins+-- together with the amount of change to send back to yourself, taking the+-- fee into account. This version uses a Sink for conduit-based coin+-- selection. chooseCoinsSink ::   (Monad m, Coin c) =>   -- | value to send   Word64 ->-  -- | fee per byte+  -- | fee per virtual byte   Word64 ->-  -- | number of outputs (including change)-  Int ->+  -- | outputs of the transaction, including the change+  [OutputKind] ->   -- | try to find better solution   Bool ->   -- | coin selection and change   ConduitT c Void m (Either String ([c], Word64))-chooseCoinsSink target fee nOut continue+chooseCoinsSink target fee outputs continue   | target > 0 =       maybeToEither err-        <$> greedyAddSink target (guessTxFee fee nOut) continue+        <$> greedyAddSink target feeOf continue   | otherwise = return $ Left "chooseCoins: Target must be > 0"   where     err = "chooseCoins: No solution found"---- | Coin selection algorithm for multisig transactions. This function returns--- the selected coins together with the amount of change to send back to--- yourself, taking the fee into account. This function assumes all the coins--- are script hash outputs that send funds to a multisignature address.-chooseMSCoins ::-  (Coin c) =>-  -- | value to send-  Word64 ->-  -- | fee per byte-  Word64 ->-  -- | m of n multisig-  (Int, Int) ->-  -- | number of outputs (including change)-  Int ->-  -- | try to find better solution-  Bool ->-  [c] ->-  -- | coin selection change amount-  Either String ([c], Word64)-chooseMSCoins target fee ms nOut continue coins =-  runIdentity . runConduit $-    sourceList coins .| chooseMSCoinsSink target fee ms nOut continue---- | Coin selection algorithm for multisig transactions. This function returns--- the selected coins together with the amount of change to send back to--- yourself, taking the fee into account. This function assumes all the coins--- are script hash outputs that send funds to a multisignature address. This--- version uses a Sink if you need conduit-based coin selection.-chooseMSCoinsSink ::-  (Monad m, Coin c) =>-  -- | value to send-  Word64 ->-  -- | fee per byte-  Word64 ->-  -- | m of n multisig-  (Int, Int) ->-  -- | number of outputs (including change)-  Int ->-  -- | try to find better solution-  Bool ->-  -- | coin selection and change-  ConduitT c Void m (Either String ([c], Word64))-chooseMSCoinsSink target fee ms nOut continue-  | target > 0 =-      maybeToEither err-        <$> greedyAddSink target (guessMSTxFee fee ms nOut) continue-  | otherwise = return $ Left "chooseMSCoins: Target must be > 0"-  where-    err = "chooseMSCoins: No solution found"+    feeOf coins = guessTxFee fee (map coinInputKind coins) outputs  -- | Select coins greedily by starting from an empty solution. If the 'continue' -- flag is set, the algorithm will try to find a better solution in the stream@@ -190,8 +143,8 @@   (Monad m, Coin c) =>   -- | value to send   Word64 ->-  -- | coin count to fee function-  (Int -> Word64) ->+  -- | fee of a transaction that spends the coins given+  ([c] -> Word64) ->   -- | try to find better solutions   Bool ->   -- | coin selection and change@@ -200,15 +153,15 @@   go [] 0 [] 0   where     -- The goal is the value we must reach (including the fee) for a certain-    -- amount of selected coins.-    goal c = target + guessFee c+    -- selection of coins.+    goal coins = target + guessFee coins     go acc aTot ps pTot =       await >>= \case         -- A coin is available in the stream         Just coin -> do           let val = coinValue coin           -- We have reached the goal using this coin-          if val + aTot >= goal (length acc + 1)+          if val + aTot >= goal (coin : acc)             then -- If we want to continue searching for better solutions               if continue                 then -- This solution is the first one or@@ -218,10 +171,10 @@                       go [] 0 (coin : acc) (val + aTot)                     else -- Otherwise, we stop here and return the previous                     -- solution-                      return $ Just (ps, pTot - goal (length ps))+                      return $ Just (ps, pTot - goal ps)                 else -- Otherwise, return this solution                   return $-                    Just (coin : acc, val + aTot - goal (length acc + 1))+                    Just (coin : acc, val + aTot - goal (coin : acc))             else -- We have not yet reached the goal. Add the coin to the             -- accumulator               go (coin : acc) (val + aTot) ps pTot@@ -232,98 +185,162 @@               then -- If no solution was found, return Nothing                 Nothing               else -- If we have a solution, return it-                Just (ps, pTot - goal (length ps))+                Just (ps, pTot - goal ps) --- | Estimate tranasction fee to pay based on transaction size estimation.-guessTxFee :: Word64 -> Int -> Int -> Word64-guessTxFee byteFee nOut nIn =-  byteFee * fromIntegral (guessTxSize nIn [] nOut 0)+{- Size and fee estimation -} --- | Same as 'guessTxFee' but for multisig transactions.-guessMSTxFee :: Word64 -> (Int, Int) -> Int -> Int -> Word64-guessMSTxFee byteFee ms nOut nIn =-  byteFee * fromIntegral (guessTxSize 0 (replicate nIn ms) nOut 0)+-- | The kind of input a coin is spent with, which decides the bytes it+-- takes once signed. Keys are compressed, as every SegWit kind requires,+-- unless the kind says otherwise. A multisig kind is @m@ of @n@, with @n@+-- up to 'maxMultisigKeys'.+--+-- Some inputs the library signs have no kind: bare multisig,+-- pay-to-script-hash or pay-to-witness-script-hash of a single key,+-- multisig with an uncompressed key, and Taproot spent by a script path.+data InputKind+  = -- | pay-to-public-key. Its input is the signature alone, so the+    -- compression of the key does not matter.+    InputPK+  | -- | pay-to-public-key-hash+    InputPKHash+  | -- | pay-to-public-key-hash with an uncompressed, 65-byte key+    InputUncompressedPKHash+  | -- | pay-to-script-hash multisig, @m@ of @n@+    InputMulSig Int Int+  | -- | pay-to-witness-public-key-hash+    InputWitnessPKHash+  | -- | pay-to-witness-script-hash multisig, @m@ of @n@+    InputWitnessMulSig Int Int+  | -- | pay-to-witness-public-key-hash nested in pay-to-script-hash+    InputNestedWitnessPKHash+  | -- | pay-to-witness-script-hash multisig nested in pay-to-script-hash+    InputNestedWitnessMulSig Int Int+  | -- | Taproot, spent by its key path+    InputTaproot+  deriving (Eq, Show, Read) --- | Computes an upper bound on the size of a transaction based on some known--- properties of the transaction.-guessTxSize ::-  -- | number of regular transaction inputs-  Int ->-  -- | multisig m of n for each input-  [(Int, Int)] ->-  -- | number of P2PKH outputs-  Int ->-  -- | number of P2SH outputs-  Int ->-  -- | upper bound on transaction size-  Int-guessTxSize pki msi pkout msout =-  8 + inpLen + inp + outLen + out-  where-    inpLen =-      B.length-        . runPutS-        . serialize-        . VarInt-        . fromIntegral-        $ length msi + pki-    outLen =-      B.length-        . runPutS-        . serialize-        . VarInt-        . fromIntegral-        $ pkout + msout-    inp = pki * 148 + sum (map guessMSSize msi)-    -- (20: hash160) + (5: opcodes) +-    -- (1: script len) + (8: Word64)-    out =-      pkout * 34-        +-        -- (20: hash160) + (3: opcodes) +-        -- (20: hash160) + (3: opcodes) +-        -- (20: hash160) + (3: opcodes) +-        -- (20: hash160) + (3: opcodes) +-        -- (1: script len) + (8: Word64)-        -- (1: script len) + (8: Word64)-        -- (1: script len) + (8: Word64)-        -- (1: script len) + (8: Word64)+-- | The kind of an output, which decides its size. An output of any other+-- script is 'OutputScript' with the size of the script, so every output+-- has a kind.+data OutputKind+  = -- | pay-to-public-key-hash+    OutputPKHash+  | -- | pay-to-script-hash+    OutputScriptHash+  | -- | pay-to-script-hash of a 256-bit hash, the P2SH32 of Bitcoin Cash+    OutputScript32Hash+  | -- | pay-to-witness-public-key-hash+    OutputWitnessPKHash+  | -- | pay-to-witness-script-hash+    OutputWitnessScriptHash+  | -- | Taproot+    OutputTaproot+  | -- | any other script, of that many bytes: bare multisig, a data+    -- carrier, or a witness program of another version+    OutputScript Int+  deriving (Eq, Show, Read) -        -- (20: hash160) + (3: opcodes) ++-- | An upper bound on the weight, in weight units of BIP 141, of a signed+-- transaction with the inputs and outputs given. A signature is counted as+-- 72 bytes with its hash type, the most a low-S DER signature takes, and a+-- Taproot signature as 65 bytes.+guessTxWeight :: [InputKind] -> [OutputKind] -> Int+guessTxWeight inputs outputs = 4 * base + witness+  where+    base =+      8 -- version and lock time+        + varIntSize (length inputs)+        + sum (map (fst . inputWeight) inputs)+        + varIntSize (length outputs)+        + sum (map outputSize outputs)+    witnessBytes = map (snd . inputWeight) inputs+    -- A transaction with a witness has the marker and the flag, and a+    -- witness stack for every input. The stack of an input without a+    -- witness is one byte, its count of zero.+    witness+      | any (> 0) witnessBytes = 2 + sum (map (max 1) witnessBytes)+      | otherwise = 0 -        -- (20: hash160) + (3: opcodes) +-        -- (1: script len) + (8: Word64)-        -- (1: script len) + (8: Word64)+-- | An upper bound on the virtual size, in virtual bytes of BIP 141, of a+-- signed transaction with the inputs and outputs given: its weight divided+-- by four, rounded up. Nodes charge fees by virtual byte.+guessTxSize :: [InputKind] -> [OutputKind] -> Int+guessTxSize inputs outputs = (guessTxWeight inputs outputs + 3) `div` 4 -        -- (20: hash160) + (3: opcodes) +-        -- (20: hash160) + (3: opcodes) +-        -- (1: script len) + (8: Word64)-        -- (1: script len) + (8: Word64)+-- | Estimate the fee to pay, from a fee rate per virtual byte and the+-- inputs and outputs of the transaction.+guessTxFee :: Word64 -> [InputKind] -> [OutputKind] -> Word64+guessTxFee feeRate inputs outputs =+  feeRate * fromIntegral (guessTxSize inputs outputs) -        -- (20: hash160) + (3: opcodes) +-        -- (1: script len) + (8: Word64)-        msout * 32+-- | The bytes an input of the kind takes: in the transaction without its+-- witness, and in the witness. A kind without a witness counts no witness+-- bytes. Its empty stack takes one byte when another input has a witness,+-- which 'guessTxWeight' counts.+inputWeight :: InputKind -> (Int, Int)+inputWeight = \case+  InputPK -> (prefix + withSize sigPush, 0)+  InputPKHash -> (prefix + withSize (sigPush + keyPush), 0)+  InputUncompressedPKHash -> (prefix + withSize (sigPush + uncompressedKeyPush), 0)+  InputMulSig m n -> (prefix + withSize (1 + m * sigPush + push (multisig m n)), 0)+  InputWitnessPKHash -> (prefix + 1, pkhWitness)+  InputWitnessMulSig m n -> (prefix + 1, multisigWitness m n)+  InputNestedWitnessPKHash -> (prefix + withSize (push 22), pkhWitness)+  InputNestedWitnessMulSig m n -> (prefix + withSize (push 34), multisigWitness m n)+  InputTaproot -> (prefix + 1, 1 + withSize 65)+  where+    -- the outpoint and the sequence+    prefix = 36 + 4+    -- a signature with its hash type, pushed: 1 + 72+    sigPush = push 72+    -- a compressed key, pushed+    keyPush = push 33+    -- an uncompressed key, pushed+    uncompressedKeyPush = push 65+    -- bytes after their count+    withSize n = varIntSize n + n+    -- OP_m, the compressed keys pushed, OP_n and OP_CHECKMULTISIG+    multisig m n = count m + 34 * n + count n + 1+    -- a count of a multisig script: OP_1 to OP_16, or a one-byte push of+    -- one byte from 17 to 'maxMultisigKeys'+    count c+      | c <= 16 = 1+      | otherwise = 2+    -- the number of items, the signature and the key+    pkhWitness = 1 + withSize 72 + withSize 33+    -- the number of items, the empty item, the signatures and the script+    multisigWitness m n = 1 + 1 + m * withSize 72 + withSize (multisig m n) --- | Size of a multisig P2SH input.-guessMSSize :: (Int, Int) -> Int-guessMSSize (m, n) =-  -- OutPoint (36) + Sequence (4) + Script-  40-    + fromIntegral (B.length $ runPutS . serialize $ VarInt $ fromIntegral scp)-    + scp+-- | The bytes an output of the kind takes: its amount, and its script with+-- its size+outputSize :: OutputKind -> Int+outputSize = \case+  OutputPKHash -> 8 + withSize 25+  OutputScriptHash -> 8 + withSize 23+  OutputScript32Hash -> 8 + withSize 35+  OutputWitnessPKHash -> 8 + withSize 22+  OutputWitnessScriptHash -> 8 + withSize 34+  OutputTaproot -> 8 + withSize 34+  OutputScript n -> 8 + withSize n   where-    -- OP_M + n*PubKey + OP_N + OP_CHECKMULTISIG+    withSize n = varIntSize n + n -    rdm =-      fromIntegral $-        B.length $-          runPutS $-            serialize $-              opPushData $-                B.replicate (n * 34 + 3) 0-    -- Redeem + m*sig + OP_0-    scp = rdm + m * 73 + 1+-- | The bytes of a push of that many bytes+push :: Int -> Int+push n+  | n <= 75 = 1 + n+  | n <= 0xff = 2 + n+  | n <= 0xffff = 3 + n+  | otherwise = 5 + n +-- | The bytes of a count as a 'VarInt'+varIntSize :: Int -> Int+varIntSize n+  | n < 0xfd = 1+  | n <= 0xffff = 3+  | n <= 0xffffffff = 5+  | otherwise = 9+ {- Build a new Tx -}  -- | Build a transaction by providing a list of outpoints as inputs@@ -351,15 +368,26 @@ -- list of private keys. The signature is computed deterministically as defined -- in RFC-6979. ----- Example: P2SH-P2WKH+-- A 'SigInput' whose script is 'PayWitnessPKHash' or 'PayWitnessScriptHash'+-- is a native SegWit input: the signature goes in the witness and the input+-- script stays empty. For a SegWit program nested in pay-to-script-hash, use+-- 'signNestedWitnessTx'. --+-- A 'SigInput' whose script is a Taproot output is signed by its key path,+-- with a key whose x-only public key is the output key, or whose tweak+-- without a script tree is the output key, as a wallet of BIP 86 spends. The+-- BIP 341 digest commits to the output every input spends, so every input+-- of the transaction must have a 'SigInput' then.+--+-- Example: P2WPKH+-- -- > sigIn = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing--- > signedTx = signTx btc unsignedTx [sigIn] [key]+-- > signedTx = signTx btc ctx unsignedTx [sigIn] [key] -- -- Example: P2SH-P2WSH multisig -- -- > sigIn = SigInput (PayWitnessScriptHash h) 100000 op sigHashAll (Just $ PayMulSig [p1,p2,p3] 2)--- > signedTx = signTx btc unsignedTx [sigIn] [k1,k3]+-- > signedTx = signNestedWitnessTx btc ctx unsignedTx [sigIn] [k1,k3] signTx ::   Network ->   Ctx ->@@ -393,7 +421,9 @@     -- NOTE: the nesting flag is ignored for non-segwit inputs     nested s = (s, True) --- | Sign a single input in a transaction deterministically (RFC-6979).+-- | Sign a single input in a transaction deterministically (RFC-6979). A+-- Taproot input is signed with 'signTaprootInput' instead, since its+-- digest needs the output every input spends. signInput ::   Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String Tx signInput net ctx tx i si =@@ -415,31 +445,35 @@  -- | Merge partially-signed multisig transactions.  This function does not -- support segwit and P2SH-segwit inputs.  Use PSBTs to merge transactions with--- segwit inputs.+-- segwit inputs. An input that no transaction has signed stays unsigned. mergeTxs ::   Network ->   Ctx ->   [Tx] ->   [(ScriptOutput, Word64, OutPoint)] ->   Either String Tx-mergeTxs net ctx txs os-  | null txs = Left "Transaction list is empty"-  | length (nub emptyTxs) /= 1 = Left "Transactions do not match"-  | length txs == 1 = return $ head txs-  | otherwise = foldM (mergeTxInput net ctx txs) (head emptyTxs) outs+mergeTxs net ctx txs os = case txs of+  [] -> Left "Transaction list is empty"+  [tx] -> return tx+  tx : _+    | length (nub emptyTxs) /= 1 -> Left "Transactions do not match"+    | otherwise -> foldM (mergeTxInput net ctx txs) (clearAll tx) (outs tx)   where-    zipOp = zip (matchTemplate os (head txs).inputs f) [0 ..]-    outs =-      map (first $ (\(o, v, _) -> (o, v)) . fromJust) $-        filter (isJust . fst) zipOp+    zipOp tx = zip (matchTemplate os tx.inputs f) [0 ..]+    outs tx =+      [ ((o, v), i)+      | (Just (o, v, _), i) <- zipOp tx+      ]     f (_, _, o) txin = o == txin.outpoint-    emptyTxs = map (\tx -> foldl clearInput tx outs) txs+    emptyTxs = map clearAll txs+    clearAll tx = foldl clearInput tx (outs tx)     ins is i = updateIndex i is (\TxIn {..} -> TxIn {script = B.empty, ..})     clearInput tx (_, i) =       Tx tx.version (ins tx.inputs i) tx.outputs [] tx.locktime  -- | Merge input from partially-signed multisig transactions.  This function--- does not support segwit and P2SH-segwit inputs.+-- does not support segwit and P2SH-segwit inputs. When no transaction has a+-- script for the input, the input is left as it is. mergeTxInput ::   Network ->   Ctx ->@@ -451,11 +485,13 @@   -- Ignore transactions with empty inputs   let ins = map ((.script) . (!! i) . (.inputs)) txs   sigRes <- mapM extractSigs $ filter (not . B.null) ins-  let rdm = snd $ head sigRes-  unless (all ((== rdm) . snd) sigRes) $ Left "Redeem scripts do not match"-  si <- marshal (net, ctx) <$> go (nub $ concatMap fst sigRes) so rdm-  let ins' = updateIndex i tx.inputs (\TxIn {..} -> TxIn {script = si, ..})-  return $ Tx tx.version ins' tx.outputs [] tx.locktime+  case sigRes of+    [] -> return tx+    (_, rdm) : _ -> do+      unless (all ((== rdm) . snd) sigRes) $ Left "Redeem scripts do not match"+      si <- marshal (net, ctx) <$> go (nub $ concatMap fst sigRes) so rdm+      let ins' = updateIndex i tx.inputs (\TxIn {..} -> TxIn {script = si, ..})+      return $ Tx tx.version ins' tx.outputs [] tx.locktime   where     go allSigs out rdmM =       case out of@@ -481,40 +517,61 @@           Right (sigs, Just rdm)         _ -> Left "Invalid script input type"     f out (TxSignature x sh) p =-      verifyHashSig-        ctx-        (txSigHash net tx (encodeOutput ctx out) val i sh)-        x-        p.point+      either (const False) (\h -> verifyHashSig ctx h x p.point) $+        encodeOutput ctx out >>= \s -> txSigHash net tx s val i sh     f _ TxSignatureEmpty _ = False  {- Tx verification -} --- | Verify if a transaction is valid and all of its inputs are standard.+-- | Verify that every input of the transaction spends one of the coins+-- given with a standard input that verifies ('verifyStdInput'), and that+-- the outputs do not spend more than the coins hold. Other rules of+-- @CheckTransaction@ in Bitcoin Core, such as the limits on sizes and+-- amounts and the absence of duplicate inputs, are not checked. verifyStdTx ::   Network -> Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Bool-verifyStdTx net ctx tx xs =-  not (null tx.inputs) && all go (zip (matchTemplate xs tx.inputs f) [0 ..])+verifyStdTx net ctx tx coins =+  not (null tx.inputs)+    && all (verifyStdInput net ctx tx coins) [0 .. length tx.inputs - 1]+    && spent <= available   where-    f (_, _, o) txin = o == txin.outpoint-    go (Just (so, val, _), i) = verifyStdInput net ctx tx i so val-    go _ = False+    spent = sum (map (toInteger . (.value)) tx.outputs)+    available = sum [toInteger v | (_, v, _) <- matched]+    matched = catMaybes (matchTemplate coins tx.inputs (\(_, _, o) txin -> o == txin.outpoint)) --- | Verify if a transaction input is valid and standard.-verifyStdInput :: Network -> Ctx -> Tx -> Int -> ScriptOutput -> Word64 -> Bool-verifyStdInput net ctx tx i so0 val-  | isSegwit so0 =-      fromRight False $ (inp == mempty &&) . verifySegwitInput so0 <$> wp so0-  | otherwise =-      or $-        rights-          [ verifyLegacyInput so0 <$> unmarshal (net, ctx) inp,-            nestedScriptOutput >>= \so -> verifyNestedInput so0 so <$> wp so-          ]+-- | Verify that an input spends one of the coins given with a standard+-- input that verifies: a signature of the right key and digest for+-- pay-to-public-key, pay-to-public-key-hash, multisig and pay-to-script-hash+-- inputs, their SegWit forms, native or nested in pay-to-script-hash, and+-- Taproot inputs spent by the key path or by a script path whose leaf is a+-- single key check. An input that is not a witness program must have no+-- witness, as @VerifyScript@ requires in Bitcoin Core. Other script path+-- spends need script execution and are not verified: they are refused.+verifyStdInput :: Network -> Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Int -> Bool+verifyStdInput net ctx tx coins i+  | i < 0 || i >= length tx.inputs = False+  | otherwise = case coinOf (tx.inputs !! i) of+      Nothing -> False+      Just (so0, val)+        | isSegwit so0 ->+            fromRight False $ (B.null inp &&) . verifySegwitInput so0 val <$> wp so0+        | isTaproot so0 ->+            B.null inp && verifyTaprootInput so0+        | otherwise ->+            or $+              rights+                [ (\si -> null ws && verifyLegacyInput so0 val si) <$> unmarshal (net, ctx) inp,+                  nestedScriptOutput >>= \so -> verifyNestedInput so0 so val <$> wp so+                ]   where     inp = (tx.inputs !! i).script-    theTxSigHash so = Sign.makeSigHash net ctx tx i so val+    coinOf txin = find (\(_, _, o) -> o == txin.outpoint) coins >>= \(so, val, _) -> Just (so, val)+    theTxSigHash so val = Sign.makeSigHash net ctx tx i so val +    -- A signature verifies only when its digest could be computed.+    checkSig :: Either String Hash256 -> Sig -> PubKey -> Bool+    checkSig h sig pub = either (const False) (\m -> verifyHashSig ctx m sig pub) h+     ws :: WitnessStack     ws       | length tx.witness > i = tx.witness !! i@@ -524,49 +581,46 @@     wp so = decodeWitnessInput net ctx =<< viewWitnessProgram net ctx so ws      nestedScriptOutput :: Either String ScriptOutput-    nestedScriptOutput =-      runGetS deserialize inp >>= dec . ops-      where-        ops (Script ops') = ops'-        dec = \case-          [OP_PUSHDATA bs _] -> unmarshal ctx bs-          _ -> Left "nestedScriptOutput: not a nested output"+    nestedScriptOutput = case unmarshal (net, ctx) inp of+      Right (NestedWitnessInput so) -> Right so+      _ -> Left "nestedScriptOutput: not a nested output" -    verifyLegacyInput :: ScriptOutput -> ScriptInput -> Bool-    verifyLegacyInput so si = case (so, si) of+    verifyLegacyInput :: ScriptOutput -> Word64 -> ScriptInput -> Bool+    verifyLegacyInput so val si = case (so, si) of       (PayPK pub, RegularInput (SpendPK (TxSignature sig sh))) ->-        verifyHashSig ctx (theTxSigHash so sh Nothing) sig pub.point+        checkSig (theTxSigHash so val sh Nothing) sig pub.point       (PayPKHash h, RegularInput (SpendPKHash (TxSignature sig sh) pub)) ->         pubKeyAddr ctx pub == p2pkhAddr h-          && verifyHashSig ctx (theTxSigHash so sh Nothing) sig pub.point+          && checkSig (theTxSigHash so val sh Nothing) sig pub.point       (PayMulSig pubs r, RegularInput (SpendMulSig sigs)) ->-        countMulSig net ctx tx out val i ((.point) <$> pubs) sigs == r+        either (const False) (\out -> countMulSig net ctx tx out val i ((.point) <$> pubs) sigs == r) $+          encodeOutput ctx so       (PayScriptHash h, ScriptHashInput si' rdm) ->-        payToScriptAddress ctx rdm == p2shAddr h && verifyLegacyInput rdm (RegularInput si')+        payToScriptAddress ctx rdm == p2shAddr h && verifyLegacyInput rdm val (RegularInput si')       _ -> False-      where-        out = encodeOutput ctx so      verifySegwitInput ::-      ScriptOutput -> (Maybe ScriptOutput, SimpleInput) -> Bool-    verifySegwitInput so (rdm, si) = case (so, rdm, si) of+      ScriptOutput -> Word64 -> (Maybe ScriptOutput, SimpleInput) -> Bool+    verifySegwitInput so val (rdm, si) = case (so, rdm, si) of       ( PayWitnessPKHash h,         Nothing,         SpendPKHash (TxSignature sig sh) pub         ) ->-          let keytest = pubKeyWitnessAddr ctx pub == p2wpkhAddr h-              sighash = theTxSigHash so sh Nothing+          -- Compare the key hash only. An uncompressed key is non-standard+          -- here but still valid by consensus, so verification accepts it.+          let keytest = pubKeyAddr ctx pub == p2pkhAddr h+              sighash = theTxSigHash so val sh Nothing               pkpoint = pub.point-              verify = verifyHashSig ctx sighash sig pkpoint+              verify = checkSig sighash sig pkpoint            in keytest && verify       ( PayWitnessScriptHash h,         Just rdm'@(PayPK pub),         SpendPK (TxSignature sig sh)         ) ->           let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h-              sighash = theTxSigHash so sh $ Just rdm'+              sighash = theTxSigHash so val sh $ Just rdm'               pkpoint = pub.point-              verify = verifyHashSig ctx sighash sig pkpoint+              verify = checkSig sighash sig pkpoint            in keytest && verify       ( PayWitnessScriptHash h,         Just rdm'@(PayPKHash kh),@@ -575,8 +629,8 @@           let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h               addrtest = addressHash (marshal ctx pub) == kh               pkpoint = pub.point-              sighash = theTxSigHash so sh $ Just rdm'-              verify = verifyHashSig ctx sighash sig pkpoint+              sighash = theTxSigHash so val sh $ Just rdm'+              verify = checkSig sighash sig pkpoint            in keytest && addrtest && verify       ( PayWitnessScriptHash h,         Just rdm'@(PayMulSig pubs r),@@ -584,17 +638,52 @@         ) ->           let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h               pkpoints = (.point) <$> pubs-              hashfun sh = theTxSigHash so sh $ Just rdm'+              hashfun sh = theTxSigHash so val sh $ Just rdm'               verify = countMulSig' ctx hashfun pkpoints sigs == r            in keytest && verify       _ -> False      verifyNestedInput ::-      ScriptOutput -> ScriptOutput -> (Maybe RedeemScript, SimpleInput) -> Bool-    verifyNestedInput so so' x = case so of-      PayScriptHash h -> payToScriptAddress ctx so' == p2shAddr h && verifySegwitInput so' x+      ScriptOutput -> ScriptOutput -> Word64 -> (Maybe RedeemScript, SimpleInput) -> Bool+    verifyNestedInput so so' val x = case so of+      PayScriptHash h -> payToScriptAddress ctx so' == p2shAddr h && verifySegwitInput so' val x       _ -> False +    -- The outputs every input spends, in order, which the Taproot digest+    -- commits to+    spentOutputs :: Maybe [TxOut]+    spentOutputs = mapM (fmap (\(so, val) -> TxOut val (marshal ctx so)) . coinOf) tx.inputs++    verifyTaprootInput :: ScriptOutput -> Bool+    verifyTaprootInput so = fromRight False $ do+      outKey <- case so of+        PayWitness 1 program -> maybe (Left "not an output key") Right (importXOnlyPubKey ctx program)+        _ -> Left "not a Taproot output"+      spent <- maybe (Left "a spent output is missing") Right spentOutputs+      let schnorr sigBytes annex leaf = do+            TaprootSignature sig sh <- decodeTaprootSig sigBytes+            h <- taprootSigHash tx spent i sh annex leaf+            return (sig, h)+      case viewTaprootWitness ctx ws of+        Just (KeyPathSpend sigBytes annex) -> do+          (sig, h) <- schnorr sigBytes annex Nothing+          return (verifySchnorr ctx outKey sig (hashToMsg h))+        Just (ScriptPathSpend spd) -> case (spd.script.ops, spd.stack) of+          ([OP_PUSHDATA keyBytes OPCODE, OP_CHECKSIG], [sigBytes])+            | spd.leafVersion == tapscriptLeafVersion -> do+                leafKey <- maybe (Left "not an x-only key") Right (importXOnlyPubKey ctx keyBytes)+                (sig, h) <- schnorr sigBytes spd.annex (Just (leafHash spd.leafVersion spd.script, 0xffffffff))+                return (verifyScriptPathData ctx outKey spd && verifySchnorr ctx leafKey sig (hashToMsg h))+          _ -> Left "script path spends other than a single key check are not verified"+        Nothing -> Left "not a Taproot witness"++-- | Whether an output script is a Taproot output: a witness program of+-- version 1 that is 32 bytes long+isTaproot :: ScriptOutput -> Bool+isTaproot = \case+  PayWitness 1 program -> B.length program == 32+  _ -> False+ -- | Count the number of valid signatures for a multi-signature transaction. countMulSig ::   Network ->@@ -611,11 +700,12 @@   where     h = txSigHash net tx out val i -countMulSig' :: Ctx -> (SigHash -> Hash256) -> [PubKey] -> [TxSignature] -> Int+countMulSig' :: Ctx -> (SigHash -> Either String Hash256) -> [PubKey] -> [TxSignature] -> Int countMulSig' _ _ [] _ = 0 countMulSig' _ _ _ [] = 0 countMulSig' ctx h (_ : pubs) (TxSignatureEmpty : sigs) =   countMulSig' ctx h pubs sigs countMulSig' ctx h (pub : pubs) sigs@(TxSignature sig sh : sigs')-  | verifyHashSig ctx (h sh) sig pub = 1 + countMulSig' ctx h pubs sigs'+  | either (const False) (\m -> verifyHashSig ctx m sig pub) (h sh) =+      1 + countMulSig' ctx h pubs sigs'   | otherwise = countMulSig' ctx h pubs sigs
src/Haskoin/Transaction/Builder/Sign.hs view
@@ -25,6 +25,7 @@     signTx,     findInputIndex,     signInput,+    signTaprootInput,     buildInput,     sigKeys,   )@@ -35,6 +36,7 @@ import Crypto.Secp256k1 import Data.Aeson import Data.Aeson.Encoding+import qualified Data.ByteString as B import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial@@ -52,12 +54,15 @@ import Haskoin.Script import Haskoin.Transaction.Common import Haskoin.Transaction.Segwit+import Haskoin.Transaction.Taproot import Haskoin.Util  -- | Data type used to specify the signing parameters of a transaction input. -- To sign an input, the previous output script, outpoint and sighash are -- required. When signing a pay to script hash output, an additional redeem--- script is required.+-- script is required. A Taproot output ('PayWitness' version 1) is signed+-- by its key path, with 'sigHashDefault' or one of the hash types+-- 'isTaprootSigHash' accepts. data SigInput = SigInput   { -- | output script to spend     script :: !ScriptOutput,@@ -122,10 +127,60 @@   | otherwise = foldM go otx $ findInputIndex ((.outpoint) . fst) sigis ti   where     ti = otx.inputs-    go tx (sigi@(SigInput so _ _ _ rdmM, _), i) = do+    go tx (sigi@(SigInput so _ _ sh rdmM, _), i) = do       keys <- sigKeys ctx so rdmM allKeys-      foldM (\t k -> signInput net ctx t i sigi k) tx keys+      if isTaprootOutput so+        then do+          spent <- spentOutputs+          foldM (\t k -> signTaprootInput ctx t spent i sh k.key) tx keys+        else foldM (\t k -> signInput net ctx t i sigi k) tx keys+    -- The outputs every input spends, which a Taproot signature commits+    -- to: a nested SegWit program is spent as its P2SH wrapper+    spentOutputs =+      forM ti $ \txin ->+        case find ((== txin.outpoint) . (.outpoint) . fst) sigis of+          Just (s, nested) -> (\so -> TxOut s.value (marshal ctx so)) <$> coinScript s nested+          Nothing -> Left "signTx: a Taproot signature needs the output every input spends"+    coinScript s nested+      | nested && isSegwit s.script = toP2SH <$> encodeOutput ctx s.script+      | otherwise = Right s.script +-- | Whether the output is a Taproot output: a witness program of version 1+-- that is 32 bytes long+isTaprootOutput :: ScriptOutput -> Bool+isTaprootOutput = \case+  PayWitness 1 program -> B.length program == 32+  _ -> False++-- | Sign a Taproot input by its key path, as BIP 341 defines it: the+-- Schnorr signature of the digest of 'taprootSigHash' goes in the witness+-- of the input, with its hash type byte unless the type is+-- 'sigHashDefault'. The secret key is the one of the output key, which+-- 'taprootOutputSecKey' derives from the internal key. The outputs spent+-- are those of every input, in order. Fails when the index is out of+-- range, when the spent outputs do not match the inputs, or when the hash+-- type is not one 'isTaprootSigHash' accepts.+signTaprootInput ::+  Ctx ->+  Tx ->+  -- | outputs spent, one per input+  [TxOut] ->+  -- | input index+  Int ->+  SigHash ->+  -- | the secret key of the output key+  SecKey ->+  Either String Tx+signTaprootInput ctx tx spent i sh secret = do+  h <- taprootSigHash tx spent i sh Nothing Nothing+  let signature = TaprootSignature (signSchnorr ctx secret (hashToMsg h)) sh+      stacks+        | null tx.witness = replicate (length tx.inputs) []+        | otherwise = tx.witness+  unless (length stacks == length tx.inputs) $+    Left "signTaprootInput: Invalid number of witness stacks"+  return tx {witness = updateIndex i stacks (const [encodeTaprootSig signature])}+ -- | Sign a single input in a transaction deterministically (RFC-6979).  The -- nesting flag only affects the behavior of segwit inputs. signInput ::@@ -138,14 +193,15 @@   PrivateKey ->   Either String Tx signInput net ctx tx i (sigIn@(SigInput so val _ _ rdmM), nest) key = do-  let sig = makeSignature net ctx tx i sigIn key+  when (isTaprootOutput so) $+    Left "signInput: a Taproot input is signed with signTaprootInput"+  sig <- makeSignature net ctx tx i sigIn key   si <- buildInput net ctx tx i so val rdmM sig $ derivePublicKey ctx key   w <- updatedWitnessData net ctx tx i so si   return tx {inputs = nextTxIn so si, witness = w}   where     f si TxIn {..} = TxIn {script = marshal (net, ctx) si, ..}-    g so' TxIn {..} = TxIn {script = pkScript so', ..}-    pkScript so' = runPutS . serialize . opPushData $ marshal ctx so'+    g so' = f (NestedWitnessInput so')     nextTxIn so' si       | isSegwit so' && nest = updateIndex i tx.inputs (g so')       | isSegwit so' = tx.inputs@@ -192,7 +248,9 @@     g (Nothing, _) = Nothing  -- | Find from the list of provided private keys which one is required to sign--- the 'ScriptOutput'.+-- the 'ScriptOutput'. For a Taproot output, the key returned is the secret+-- key of the output key: a key whose x-only public key is the output key,+-- or the tweak of a key without a script tree, as BIP 86 spends. sigKeys ::   Ctx ->   ScriptOutput ->@@ -209,6 +267,9 @@     (PayScriptHash _, Just rdm) -> sigKeys ctx rdm Nothing keys     (PayWitnessPKHash h, _) -> return $ keyByHash h     (PayWitnessScriptHash _, Just rdm) -> sigKeys ctx rdm Nothing keys+    (PayWitness 1 program, Nothing)+      | B.length program == 32 ->+          return [PrivateKey k True | Just k <- map (taprootKey program) keys]     _ -> Left "sigKeys: Could not decode output script"   where     zipKeys =@@ -220,6 +281,14 @@       ]     keyByHash h = fmap fst . maybeToList . findKey h $ zipKeys     findKey h = find $ (== Just h) . addressHash160 . pubKeyAddr ctx . snd+    -- The secret key of a Taproot output key: the key itself when its+    -- x-only public key is the output key, or its tweak without a tree+    taprootKey program k+      | exportXOnlyPubKey ctx (derivePubKey ctx k) == program = Just k+      | Just tweaked <- taprootOutputSecKey ctx k Nothing,+        exportXOnlyPubKey ctx (derivePubKey ctx tweaked) == program =+          Just tweaked+      | otherwise = Nothing  -- | Construct an input for a transaction given a signature, public key and data -- about the previous output.@@ -240,7 +309,8 @@   PublicKey ->   Either String ScriptInput buildInput net ctx tx i so val rdmM sig pub = do-  when (i >= length tx.inputs) $ Left "buildInput: Invalid input index"+  when (i < 0 || i >= length tx.inputs) $ Left "buildInput: Invalid input index"+  when (isTaprootOutput so) $ Left "buildInput: a Taproot input has no input script"   case (so, rdmM) of     (PayScriptHash _, Just rdm) ->       buildScriptHashInput rdm@@ -264,11 +334,8 @@       inp <- buildRegularInput rdm       return $ ScriptHashInput inp.get rdm     f (TxSignature x sh) p =-      verifyHashSig-        ctx-        (makeSigHash net ctx tx i so val sh rdmM)-        x-        p.point+      either (const False) (\m -> verifyHashSig ctx m x p.point) $+        makeSigHash net ctx tx i so val sh rdmM     f TxSignatureEmpty _ = False  -- | Apply heuristics to extract the signatures for a particular input that are@@ -285,15 +352,35 @@     scp = (tx.inputs !! i).script     witSigs       | not $ isSegwit so = []-      | null tx.witness = []+      | length tx.witness <= i = []       | otherwise = rights $ decodeTxSig net ctx <$> (tx.witness !! i) --- | Produce a structured representation of a deterministic (RFC-6979) signature over an input.-makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> TxSignature-makeSignature net ctx tx i (SigInput so val _ sh rdmM) key =-  TxSignature (signHash ctx key.key m) sh+-- | Produce a structured representation of a deterministic (RFC-6979)+-- signature over an input. On a network with a fork id, such as Bitcoin+-- Cash, the 'SIGHASH_FORKID' flag is added to the 'SigHash' of the+-- 'SigInput', since every signature there must carry it. Fails when the+-- input index is out of range or the output script cannot be encoded.+--+-- A legacy input signed with 'SIGHASH_SINGLE' at an index that has no+-- output is refused. The digest of that case is the constant 1, which+-- commits to nothing, so the signature would spend any other coin of the+-- key wherever the same case comes up. Bitcoin Core computes that digest+-- in @SignatureHash@, and so does 'txSigHash', since a node must verify+-- such signatures; a signer has no reason to make one. The BIP 143 digest+-- of a SegWit input and the fork id digest of Bitcoin Cash commit to the+-- coin in that case, so those are signed.+makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String TxSignature+makeSignature net ctx tx i (SigInput so val _ sh rdmM) key = do+  m <- makeSigHash net ctx tx i so val sh' rdmM+  when (signsConstantDigest && i >= length tx.outputs) $+    Left "makeSignature: SIGHASH_SINGLE without an output at the input index"+  return $ TxSignature (signHash ctx key.key m) sh'   where-    m = makeSigHash net ctx tx i so val sh rdmM+    sh'+      | isJust net.sigHashForkId = setForkIdFlag sh+      | otherwise = sh+    signsConstantDigest =+      isSigHashSingle sh' && not (isSegwit so) && isNothing net.sigHashForkId  -- | A function which selects the digest algorithm and parameters as appropriate --@@ -307,8 +394,12 @@   Word64 ->   SigHash ->   Maybe RedeemScript ->-  Hash256-makeSigHash net ctx tx i so val sh rdmM = h net tx (encodeOutput ctx so') val i sh+  Either String Hash256+makeSigHash net ctx tx i so val sh rdmM = do+  when (isTaprootOutput so) $+    Left "makeSigHash: the digest of a Taproot input is taprootSigHash"+  script <- encodeOutput ctx so'+  h net tx script val i sh   where     so' = case so of       PayWitnessPKHash h' -> PayPKHash h'
src/Haskoin/Transaction/Common.hs view
@@ -29,9 +29,14 @@     WitnessStack,     WitnessStackItem,     txHash,+    txWitnessHash,+    txWeight,+    txVirtualSize,     hexToTxHash,     txHashToHex,     nosigTxHash,+    hasWitness,+    parseLegacyTx,     nullOutPoint,   ) where@@ -42,7 +47,6 @@ import Data.Aeson as A import Data.Aeson.Encoding qualified as E import Data.Binary (Binary (..))-import Data.Bool (bool) import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.ByteString.Builder (char7)@@ -56,14 +60,17 @@ import Data.String (IsString, fromString) import Data.String.Conversions (cs) import Data.Text (Text)-import Data.Word (Word32, Word64)+import Data.Word (Word32, Word64, Word8) import GHC.Generics (Generic) import Haskoin.Crypto.Hash import Haskoin.Network.Common import Haskoin.Util.Helpers import Text.Read as R --- | Transaction id: hash of transaction excluding witness data.+-- | A hash that identifies a transaction: the transaction id of 'txHash',+-- which leaves the witness out, or the witness transaction id of+-- 'txWitnessHash', which includes it. Shown in hex with the bytes reversed,+-- as block explorers and Bitcoin Core show it. newtype TxHash = TxHash {get :: Hash256}   deriving (Eq, Ord, Generic)   deriving newtype (Hashable, NFData)@@ -102,12 +109,14 @@   toJSON = A.String . txHashToHex   toEncoding = hexEncoding . BL.reverse . runPutL . serialize --- | Transaction hash excluding signatures.+-- | Hash of the transaction with every input script and the whole witness+-- removed, so that it is the same before and after signing, whether the+-- signatures go in the input scripts or in the witness. nosigTxHash :: Tx -> TxHash nosigTxHash Tx {..} =   TxHash . doubleSHA256 . runPutS $ serialize tx   where-    tx = Tx {inputs = map clr inputs, ..}+    tx = Tx {inputs = map clr inputs, witness = [], ..}     clr TxIn {..} = TxIn {script = B.empty, ..}  -- | Convert transaction hash to hex form, reversing bytes.@@ -115,10 +124,12 @@ txHashToHex (TxHash h) =   encodeHex . B.reverse . runPutS $ serialize h --- | Convert transaction hash from hex, reversing bytes.+-- | Convert transaction hash from hex, reversing bytes. The hex must be+-- exactly 32 bytes long, as @uint256::FromHex@ requires in Bitcoin Core. hexToTxHash :: Text -> Maybe TxHash hexToTxHash hex = do   bs <- B.reverse <$> decodeHex hex+  guard (B.length bs == 32)   h <- either (const Nothing) Just (runGetS deserialize bs)   return $ TxHash h @@ -139,28 +150,69 @@     inputs :: ![TxIn],     -- | list of transaction outputs     outputs :: ![TxOut],-    -- | witness data for the transaction+    -- | witness data for the transaction: one stack per input, or empty+    -- when no input has a witness. A transaction whose stacks are all empty+    -- has no witness: it is serialized without the witness marker, as+    -- Bitcoin Core serializes it, and reads back with an empty list here.     witness :: !WitnessData,     -- | earliest mining height or time     locktime :: !Word32   }   deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData) --- | Compute transaction hash.+-- | Compute transaction hash: the transaction id, which leaves the witness+-- out. txHash :: Tx -> TxHash txHash tx = TxHash . doubleSHA256 . runPutS $ serialize tx {witness = []} +-- | The witness transaction id of BIP 141: the hash of the transaction with+-- its witness. It is the same as 'txHash' when the transaction has no+-- witness, as @GetWitnessHash@ gives it in Bitcoin Core.+txWitnessHash :: Tx -> TxHash+txWitnessHash = TxHash . doubleSHA256 . runPutS . serialize++-- | The weight of the transaction in weight units, as BIP 141 defines it:+-- three times the size without the witness, plus the size with it.+txWeight :: Tx -> Int+txWeight tx =+  3 * B.length (runPutS (serialize tx {witness = []})) + B.length (runPutS (serialize tx))++-- | The virtual size of the transaction in virtual bytes, as BIP 141+-- defines it: the weight divided by four, rounded up. Fees are charged by+-- virtual byte.+txVirtualSize :: Tx -> Int+txVirtualSize tx = (txWeight tx + 3) `div` 4++-- | Whether any input of the transaction has a witness, as @HasWitness@+-- tells in Bitcoin Core. A transaction whose stacks are all empty has+-- none.+hasWitness :: Tx -> Bool+hasWitness = any (not . null) . (.witness)++-- | A transaction from its hex. The hex must hold the transaction and+-- nothing else, as @DecodeHexTx@ requires in Bitcoin Core. instance IsString Tx where   fromString =-    fromMaybe e . (eitherToMaybe . runGetS deserialize <=< decodeHex) . cs+    fromMaybe e . (eitherToMaybe . runGetS whole <=< decodeHex) . cs     where+      whole = do+        tx <- deserialize+        done <- isEmpty+        unless done $ fail "Trailing bytes after transaction"+        return tx       e = error "Could not read transaction from hex string"  instance Serial Tx where-  deserialize = isWitnessTx >>= bool parseLegacyTx parseWitnessTx+  deserialize = do+    flags <- lookAhead witnessFlags+    case flags of+      Nothing -> parseLegacyTx+      Just 0x00 -> parseLegacyTx+      Just 0x01 -> parseWitnessTx+      Just other -> fail $ "Unknown transaction optional data: flags " ++ show other   serialize tx-    | null tx.witness = putLegacyTx tx-    | otherwise = putWitnessTx tx+    | hasWitness tx = putWitnessTx tx+    | otherwise = putLegacyTx tx  instance Binary Tx where   put = serialize@@ -172,10 +224,8 @@  putInOut :: (MonadPut m) => Tx -> m () putInOut tx = do-  putVarInt $ length tx.inputs-  mapM_ serialize tx.inputs-  putVarInt $ length tx.outputs-  mapM_ serialize tx.outputs+  putVarIntList serialize tx.inputs+  putVarIntList serialize tx.outputs  -- | Non-SegWit transaction serializer. putLegacyTx :: (MonadPut m) => Tx -> m ()@@ -184,72 +234,73 @@   putInOut tx   putWord32le tx.locktime --- | Witness transaciton serializer.+-- | Witness transaction serializer. One stack is written per input: an+-- input without a stack gets an empty one, and stacks beyond the inputs+-- are not written. putWitnessTx :: (MonadPut m) => Tx -> m () putWitnessTx tx = do   putWord32le tx.version   putWord8 0x00   putWord8 0x01   putInOut tx-  putWitnessData tx.witness+  putWitnessData $ take n (tx.witness ++ repeat [])   putWord32le tx.locktime+  where+    n = length tx.inputs -isWitnessTx :: (MonadGet m) => m Bool-isWitnessTx = lookAhead $ do+-- | The flags byte of the extended format of BIP 144, when the transaction+-- has one: the byte after an input count of zero, as @UnserializeTransaction@+-- reads it in Bitcoin Core. A transaction whose input count is not zero has+-- no flags. Only the flags 0x01, the witness, and 0x00, which Bitcoin Core+-- reads as a transaction with no inputs and no outputs, are known; any other+-- value fails the parse there with "Unknown transaction optional data".+witnessFlags :: (MonadGet m) => m (Maybe Word8)+witnessFlags = do   _ <- getWord32le-  m <- getWord8-  f <- getWord8-  return (m == 0x00 && f == 0x01)+  marker <- getWord8+  if marker == 0x00+    then Just <$> getWord8+    else return Nothing --- | Non-SegWit transaction deseralizer.+-- | Non-SegWit transaction deserializer: the old format, without the+-- marker, flags and witness of BIP 144. Use it where the bytes are known+-- to be in that format, as the unsigned transaction of a PSBT is. There a+-- transaction with no inputs reads as such, whereas 'deserialize' reads+-- the byte after an empty input list as the flags of BIP 144, as Bitcoin+-- Core does when it allows a witness. Every count is bounded by+-- 'maxListSize', as @ReadCompactSize@ bounds it in Bitcoin Core. parseLegacyTx :: (MonadGet m) => m Tx parseLegacyTx = do   version <- getWord32le-  inputs <- rl =<< deserialize-  outputs <- rl =<< deserialize-  when (length inputs == 0x00 && length outputs == 0x01) $-    fail "Witness transaction"+  inputs <- getVarIntList deserialize+  outputs <- getVarIntList deserialize   locktime <- getWord32le   return Tx {witness = [], ..}-  where-    rl (VarInt c) = replicateM (fromIntegral c) deserialize --- | Witness transaction deserializer.+-- | Witness transaction deserializer. A transaction with the witness marker+-- must have a witness: one whose stacks are all empty fails, as it does in+-- Bitcoin Core ("Superfluous witness record"). parseWitnessTx :: (MonadGet m) => m Tx parseWitnessTx = do   version <- getWord32le   m <- getWord8   f <- getWord8   unless (m == 0x00 && f == 0x01) $ fail "Not a witness transaction"-  inputs <- replicateList =<< deserialize-  outputs <- replicateList =<< deserialize+  inputs <- getVarIntList deserialize+  outputs <- getVarIntList deserialize   witness <- parseWitnessData $ length inputs+  unless (any (not . null) witness) $+    fail "Superfluous witness record"   locktime <- getWord32le   return Tx {..}-  where-    replicateList (VarInt c) = replicateM (fromIntegral c) deserialize  -- | Witness data deserializer. Requires count of inputs. parseWitnessData :: (MonadGet m) => Int -> m WitnessData-parseWitnessData n = replicateM n parseWitnessStack-  where-    parseWitnessStack = do-      VarInt i <- deserialize-      replicateM (fromIntegral i) parseWitnessStackItem-    parseWitnessStackItem = do-      VarInt i <- deserialize-      getByteString $ fromIntegral i+parseWitnessData n = replicateM n (getVarIntList getVarIntBytes)  -- | Witness data serializer. putWitnessData :: (MonadPut m) => WitnessData -> m ()-putWitnessData = mapM_ putWitnessStack-  where-    putWitnessStack ws = do-      putVarInt $ length ws-      mapM_ putWitnessStackItem ws-    putWitnessStackItem bs = do-      putVarInt $ B.length bs-      putByteString bs+putWitnessData = mapM_ (putVarIntList putVarIntBytes)  instance FromJSON Tx where   parseJSON = withObject "Tx" $ \o ->@@ -296,14 +347,11 @@  instance Serial TxIn where   deserialize =-    TxIn <$> deserialize <*> (readBS =<< deserialize) <*> getWord32le-    where-      readBS (VarInt len) = getByteString $ fromIntegral len+    TxIn <$> deserialize <*> getVarIntBytes <*> getWord32le    serialize (TxIn o s q) = do     serialize o-    putVarInt $ B.length s-    putByteString s+    putVarIntBytes s     putWord32le q  instance Binary TxIn where@@ -347,15 +395,11 @@   deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)  instance Serial TxOut where-  deserialize = do-    val <- getWord64le-    VarInt len <- deserialize-    TxOut val <$> getByteString (fromIntegral len)+  deserialize = TxOut <$> getWord64le <*> getVarIntBytes    serialize (TxOut o s) = do     putWord64le o-    putVarInt $ B.length s-    putByteString s+    putVarIntBytes s  instance Binary TxOut where   put = serialize
src/Haskoin/Transaction/Genesis.hs view
@@ -3,40 +3,69 @@ -- | -- Module      : Haskoin.Transaction.Genesis -- Copyright   : No rights reserved--- License     : UNLICENSE+-- License     : MIT -- Maintainer  : jprupp@protonmail.ch -- Stability   : experimental -- Portability : POSIX ----- Code related to transactions parsing and serialization.-module Haskoin.Transaction.Genesis (genesisTx) where+-- Coinbase transactions of the genesis blocks.+module Haskoin.Transaction.Genesis+  ( mainnetGenesisTx,+    testnet4GenesisTx,+    genesisCoinbase,+  )+where -import Crypto.Secp256k1 (Ctx, importPubKey)-import Data.Bytes.Get (runGetS)-import Haskoin.Crypto.Keys.Common-import Haskoin.Script.Standard+import Data.ByteString (ByteString)+import Data.Maybe (fromMaybe)+import Data.Text (Text) import Haskoin.Transaction.Common-import Haskoin.Util+import Haskoin.Util.Helpers (decodeHex) --- | Transaction from Genesis block.-genesisTx :: Ctx -> Tx-genesisTx ctx =-  Tx 1 [txin] [txout] [] locktime+-- | Build a genesis coinbase the way Bitcoin Core's @CreateGenesisBlock@+-- does: version 1, one input that spends nothing, one output of 50 BTC and a+-- lock time of zero.+genesisCoinbase ::+  -- | input script: the bits, the number 4 and the message+  ByteString ->+  -- | output script+  ByteString ->+  Tx+genesisCoinbase inputScript outputScript =+  Tx 1 [TxIn (OutPoint nullHash maxBound) inputScript maxBound] [TxOut 5000000000 outputScript] [] 0   where-    txin = TxIn outpoint inputBS maxBound-    txout = TxOut 5000000000 (marshal ctx output)-    locktime = 0-    outpoint = OutPoint z maxBound-    Just inputBS =-      decodeHex+    nullHash = "0000000000000000000000000000000000000000000000000000000000000000"++-- | Coinbase of the Bitcoin genesis block. Every network except Bitcoin+-- testnet4 reuses it. Its hash is+-- @4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b@.+mainnetGenesisTx :: Tx+mainnetGenesisTx =+  genesisCoinbase+    ( hex         "04ffff001d0104455468652054696d65732030332f4a616e2f323030392043686\         \16e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f\         \757420666f722062616e6b73"-    Just pubKeyBS =-      decodeHex-        "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb\-        \649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"-    Right pubKey =-      unmarshal ctx pubKeyBS-    output = PayPK pubKey-    z = "0000000000000000000000000000000000000000000000000000000000000000"+    )+    ( hex+        "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61d\+        \eb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d\+        \5fac"+    )++-- | Coinbase of the Bitcoin testnet4 genesis block. The message is+-- @03/May/2024 000000000000000000001ebd58c244970b3aa9d783bb001011fbe8ea8e98e00e@+-- and the output pays to a public key of 33 zero bytes. Its hash is+-- @7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e@.+testnet4GenesisTx :: Tx+testnet4GenesisTx =+  genesisCoinbase+    ( hex+        "04ffff001d01044c4c30332f4d61792f32303234203030303030303030303030\+        \3030303030303030303165626435386332343439373062336161396437383362\+        \623030313031316662653865613865393865303065"+    )+    (hex "21000000000000000000000000000000000000000000000000000000000000000000ac")++hex :: Text -> ByteString+hex = fromMaybe (error "genesis script is not valid hex") . decodeHex
− src/Haskoin/Transaction/Partial.hs
@@ -1,949 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ImportQualifiedPost #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE NoFieldSelectors #-}---- |--- Module      : Haskoin.Transaction.Partial--- Copyright   : No rights reserved--- License     : MIT--- Maintainer  : matt@bitnomial.com--- Stability   : experimental--- Portability : POSIX------ Code related to PSBT parsing and serialization.-module Haskoin.Transaction.Partial-  ( -- * Partially-Signed Transactions-    PSBT (..),-    getPSBT,-    putPSBT,-    Input (..),-    getInput,-    putInput,-    Output (..),-    getOutput,-    putOutput,-    UnknownMap (..),-    Key (..),-    merge,-    mergeMany,-    mergeInput,-    mergeOutput,-    complete,-    finalTransaction,-    emptyPSBT,-    emptyInput,-    emptyOutput,--    -- ** Signing-    PSBTSigner,-    getSignerKey,-    secKeySigner,-    xPrvSigner,-    signPSBT,-  )-where--import Control.Applicative ((<|>))-import Control.DeepSeq (NFData)-import Control.Monad (foldM, guard, replicateM, unless, void)-import Crypto.Secp256k1-import Data.Binary (Binary (..))-import Data.ByteString (ByteString)-import Data.ByteString qualified as B-import Data.Bytes.Get-import Data.Bytes.Put-import Data.Bytes.Serial (Serial (..))-import Data.Either (fromRight)-import Data.HashMap.Strict (HashMap)-import Data.HashMap.Strict qualified as HashMap-import Data.Hashable (Hashable)-import Data.List (foldl')-import Data.Maybe (fromMaybe, isJust)-import Data.Serialize (Get, Put, Serialize (..))-import Data.Serialize qualified as S-import Data.String.Conversions (cs)-import GHC.Generics (Generic)-import GHC.Word (Word32, Word8)-import Haskoin.Address (Address, pubKeyAddr, pubKeyAddress)-import Haskoin.Crypto.Keys.Common-import Haskoin.Crypto.Keys.Extended-import Haskoin.Network.Common-import Haskoin.Network.Data-import Haskoin.Script.Common-import Haskoin.Script.SigHash-import Haskoin.Script.Standard-import Haskoin.Transaction.Builder.Sign-import Haskoin.Transaction.Common-import Haskoin.Transaction.Segwit-import Haskoin.Util-import Numeric (showHex)---- | 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--- 'PSBT' line up by index with the inputs and outputs in--- the unsigned transaction.-data PSBT = PSBT-  { unsignedTransaction :: Tx,-    globalUnknown :: UnknownMap,-    inputs :: [Input],-    outputs :: [Output]-  }-  deriving (Show, Read, Eq, Generic)-  deriving anyclass (NFData)---- | 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,-    partialSigs :: HashMap PublicKey ByteString,-    sigHashType :: Maybe SigHash,-    inputRedeemScript :: Maybe Script,-    inputWitnessScript :: Maybe Script,-    inputHDKeypaths :: HashMap PublicKey (Fingerprint, [KeyIndex]),-    finalScriptSig :: Maybe Script,-    finalScriptWitness :: Maybe WitnessStack,-    inputUnknown :: UnknownMap-  }-  deriving (Show, Read, Eq, Generic)-  deriving anyclass (NFData)---- | Outputs can contain information needed to spend the output at a later date.-data Output = Output-  { outputRedeemScript :: Maybe Script,-    outputWitnessScript :: Maybe Script,-    outputHDKeypaths :: HashMap PublicKey (Fingerprint, [KeyIndex]),-    outputUnknown :: UnknownMap-  }-  deriving (Show, Read, Eq, Generic)-  deriving anyclass (NFData)---- | 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, Read, Eq, Generic)-  deriving newtype (Semigroup, Monoid, NFData)---- | Raw keys for the map type used in PSBTs.-data Key = Key-  { keyType :: Word8,-    key :: ByteString-  }-  deriving (Show, Read, Eq, Generic, NFData, Hashable)---- | Take two 'PSBT's and merge them. The--- 'unsignedTransaction' field in both must be the same.-merge ::-  PSBT ->-  PSBT ->-  Maybe PSBT-merge psbt1 psbt2-  | psbt1.unsignedTransaction == psbt2.unsignedTransaction =-      Just $-        psbt1-          { globalUnknown = psbt1.globalUnknown <> psbt2.globalUnknown,-            inputs = zipWith mergeInput psbt1.inputs psbt2.inputs,-            outputs = zipWith mergeOutput psbt1.outputs psbt2.outputs-          }-merge _ _ = Nothing---- | A version of 'merge' for a collection of PSBTs.------ @since 0.21.0-mergeMany :: [PSBT] -> Maybe PSBT-mergeMany (psbt : psbts) = foldM merge psbt psbts-mergeMany _ = Nothing--mergeInput :: Input -> Input -> Input-mergeInput a b =-  Input-    { nonWitnessUtxo =-        if isJust witUtx-          then Nothing-          else a.nonWitnessUtxo <|> b.nonWitnessUtxo,-      witnessUtxo =-        witUtx,-      sigHashType =-        a.sigHashType <|> b.sigHashType,-      partialSigs =-        a.partialSigs <> b.partialSigs,-      inputHDKeypaths =-        a.inputHDKeypaths <> b.inputHDKeypaths,-      inputUnknown =-        a.inputUnknown <> b.inputUnknown,-      inputRedeemScript =-        a.inputRedeemScript <|> b.inputRedeemScript,-      inputWitnessScript =-        a.inputWitnessScript <|> b.inputWitnessScript,-      finalScriptSig =-        a.finalScriptSig <|> b.finalScriptSig,-      finalScriptWitness =-        a.finalScriptWitness <|> b.finalScriptWitness-    }-  where-    witUtx = a.witnessUtxo <|> b.witnessUtxo--mergeOutput :: Output -> Output -> Output-mergeOutput a b =-  Output-    { outputRedeemScript =-        a.outputRedeemScript <|> b.outputRedeemScript,-      outputWitnessScript =-        a.outputWitnessScript <|> b.outputWitnessScript,-      outputHDKeypaths =-        a.outputHDKeypaths <> b.outputHDKeypaths,-      outputUnknown =-        a.outputUnknown <> b.outputUnknown-    }---- | A abstraction which covers varying key configurations.  Use the 'Semigroup'--- instance to create signers for sets of keys: `signerA <> signerB` can sign--- anything for which `signerA` or `signerB` could sign.------ @since 0.21@-newtype PSBTSigner = PSBTSigner-  { unPSBTSigner ::-      PublicKey ->-      Maybe (Fingerprint, DerivPath) ->-      Maybe SecKey-  }--instance Semigroup PSBTSigner where-  PSBTSigner signer1 <> PSBTSigner signer2 =-    PSBTSigner $ \pubKey origin ->-      signer1 pubKey origin <|> signer2 pubKey origin--instance Monoid PSBTSigner where-  mempty = PSBTSigner $ \_ _ -> Nothing---- | Fetch the secret key for the given 'PublicKey' if possible.------ @since 0.21@-getSignerKey :: PSBTSigner -> PublicKey -> Maybe (Fingerprint, DerivPath) -> Maybe SecKey-getSignerKey = (.unPSBTSigner)---- | This signer can sign for one key.------ @since 0.21@-secKeySigner :: Ctx -> SecKey -> PSBTSigner-secKeySigner ctx theSecKey =-  PSBTSigner signer-  where-    signer requiredKey _-      | requiredKey.point == derivePubKey ctx theSecKey = Just theSecKey-      | otherwise = Nothing---- | This signer can sign with any child key, provided that derivation information is present.------ @since 0.21@-xPrvSigner ::-  Ctx ->-  XPrvKey ->-  -- | Origin data, if the input key is explicitly a child key-  Maybe (Fingerprint, DerivPath) ->-  PSBTSigner-xPrvSigner ctx xprv origin = PSBTSigner signer-  where-    signer pubKey (Just hdData)-      | result@(Just theSecKey) <- maybe noOrigin onOrigin origin hdData,-        pubKey.point == derivePubKey ctx theSecKey =-          result-    signer _ _ = Nothing--    noOrigin (fp, path)-      | thisFP == fp = Just $ deriveSecKey path-      | otherwise = Nothing--    onOrigin (originFP, originPath) (fp, path)-      | thisFP == fp = Just $ deriveSecKey path-      | originFP == fp =-          deriveSecKey <$> adjustPath (pathToList originPath) (pathToList path)-      | otherwise = Nothing--    deriveSecKey path = (derivePath ctx path xprv).key--    thisFP = xPubFP ctx $ deriveXPubKey ctx xprv--    -- The origin path should be a prefix of the target path if we match the-    -- origin fingerprint.  We need to remove this prefix.-    adjustPath :: [KeyIndex] -> [KeyIndex] -> Maybe DerivPath-    adjustPath (originIx : originTail) (thisIx : thisTail)-      | originIx == thisIx = adjustPath originTail thisTail-      | otherwise = Nothing-    adjustPath [] thePath = Just $ listToPath thePath-    adjustPath _ _ = Nothing---- | Update a PSBT with signatures when possible.  This function uses--- 'inputHDKeypaths' in order to calculate secret keys.------ @since 0.21@-signPSBT ::-  Network ->-  Ctx ->-  PSBTSigner ->-  PSBT ->-  PSBT-signPSBT net ctx signer PSBT {..} =-  PSBT {inputs = f <$> zip [0 ..] inputs, ..}-  where-    f = addSigsForInput net ctx signer unsignedTransaction--addSigsForInput :: Network -> Ctx -> PSBTSigner -> Tx -> (Int, Input) -> Input-addSigsForInput net ctx signer tx (ix, input) =-  maybe input (onPrevTxOut net ctx signer tx ix input) $-    Left <$> input.nonWitnessUtxo <|> Right <$> input.witnessUtxo--onPrevTxOut ::-  Network ->-  Ctx ->-  PSBTSigner ->-  Tx ->-  Int ->-  Input ->-  Either Tx TxOut ->-  Input-onPrevTxOut net ctx signer tx ix input prevTxData =-  input-    { partialSigs = newSigs <> input.partialSigs-    }-  where-    newSigs = HashMap.mapWithKey sigForInput sigKeys-    sigForInput thePubKey theSecKey =-      encodeTxSig net ctx . makeSignature net ctx tx ix theSigInput $-        PrivateKey theSecKey thePubKey.compress--    theSigInput =-      SigInput-        { -- Must be the segwit input script for segwit spends (even nested)-          script = fromMaybe theInputScript segwitInput,-          value = prevTxOut.value,-          outpoint = thePrevOutPoint,-          sighash = fromMaybe sigHashAll input.sigHashType,-          -- Must be the witness script for segwit spends (even nested)-          redeem = theWitnessScript <|> theRedeemScript-        }--    prevTxOut =-      let rf = ((!! (fromIntegral . (.index)) thePrevOutPoint) . (.outputs))-       in either rf id prevTxData-    thePrevOutPoint = (tx.inputs !! ix).outpoint--    segwitInput = justWhen isSegwit theInputScript <|> (justWhen isSegwit =<< theRedeemScript)--    theInputScript = fromRight inputScriptErr $ (unmarshal ctx . (.script)) prevTxOut-    inputScriptErr = error "addSigsForInput: Unable to decode input script"--    theRedeemScript = case decodeOutput ctx <$> input.inputRedeemScript of-      Just (Right script) -> Just script-      Just Left {} -> error "addSigsForInput: Unable to decode redeem script"-      _ -> Nothing--    theWitnessScript = case decodeOutput ctx <$> input.inputWitnessScript of-      Just (Right script) -> Just script-      Just Left {} -> error "addSigsForInput: Unable to decode witness script"-      _ -> Nothing--    sigKeys = HashMap.mapMaybeWithKey getSignerKey input.inputHDKeypaths-    getSignerKey pubKey (fp, ixs) = (.unPSBTSigner) signer pubKey $ Just (fp, listToPath ixs)---- | Take partial signatures from all of the 'Input's and finalize the signature.-complete ::-  Ctx ->-  PSBT ->-  PSBT-complete ctx PSBT {..} =-  PSBT {inputs = map (completeInput . analyzeInputs) (indexed inputs), ..}-  where-    analyzeInputs (i, input) =-      (,)-        (outputScript =<< input.witnessUtxo <|> nonWitScript)-        input-      where-        nonWitScript = getPrevOut i =<< input.nonWitnessUtxo--    getPrevOut i tx =-      (tx.outputs !!?) . fromIntegral . (.outpoint.index)-        =<< unsignedTransaction.inputs !!? i-    xs !!? i = lookup i $ indexed xs--    outputScript = eitherToMaybe . unmarshal ctx . (.script)--    completeInput (Nothing, input) = input-    completeInput (Just script, input) = pruneInputFields $ completeSig ctx input script--    -- If we have final scripts, we can get rid of data for signing following-    -- the Bitcoin Core implementation.-    pruneInputFields input-      | isJust input.finalScriptSig || isJust input.finalScriptWitness =-          input-            { partialSigs = mempty,-              inputHDKeypaths = mempty,-              inputRedeemScript = Nothing,-              inputWitnessScript = Nothing,-              sigHashType = Nothing-            }-      | otherwise = input--    indexed :: [a] -> [(Word32, a)]-    indexed = zip [0 ..]--completeSig :: Ctx -> Input -> ScriptOutput -> Input-completeSig ctx input (PayPK k) =-  input-    { finalScriptSig =-        eitherToMaybe . runGetS deserialize-          =<< HashMap.lookup k input.partialSigs-    }-completeSig ctx input (PayPKHash h)-  | [(k, sig)] <- HashMap.toList input.partialSigs,-    pubKeyAddress h == pubKeyAddr ctx k =-      input-        { finalScriptSig =-            Just $-              Script-                [ opPushData sig,-                  opPushData (marshal ctx k)-                ]-        }-completeSig ctx input (PayMulSig pubKeys m)-  | length sigs >= m =-      input {finalScriptSig = Just finalSig}-  where-    sigs = collectSigs m pubKeys input-    finalSig = Script $ OP_0 : map opPushData sigs-completeSig ctx input (PayScriptHash h)-  | Just rdmScript <- input.inputRedeemScript,-    PayScriptHash h == toP2SH rdmScript,-    Right decodedScript <- decodeOutput ctx rdmScript,-    not (isPayScriptHash decodedScript) =-      pushScript rdmScript $ completeSig ctx input decodedScript-  where-    pushScript rdmScript updatedInput =-      updatedInput-        { finalScriptSig =-            Just $-              fromMaybe (Script mempty) updatedInput.finalScriptSig-                `scriptAppend` serializedRedeemScript rdmScript-        }-    scriptAppend (Script script1) (Script script2) = Script $ script1 <> script2-completeSig ctx input (PayWitnessPKHash h)-  | [(k, sig)] <- HashMap.toList input.partialSigs,-    pubKeyAddress h == pubKeyAddr ctx k =-      input {finalScriptWitness = Just [sig, marshal ctx k]}-completeSig ctx input (PayWitnessScriptHash h)-  | Just witScript <- input.inputWitnessScript,-    PayWitnessScriptHash h == toP2WSH witScript,-    Right decodedScript <- decodeOutput ctx witScript =-      completeWitnessSig ctx input decodedScript-completeSig _ input _ = input--serializedRedeemScript :: Script -> Script-serializedRedeemScript = Script . pure . opPushData . runPutS . serialize--completeWitnessSig :: Ctx -> Input -> ScriptOutput -> Input-completeWitnessSig ctx input script@(PayMulSig pubKeys m)-  | length sigs >= m =-      input {finalScriptWitness = Just finalWit}-  where-    sigs = collectSigs m pubKeys input-    finalWit = mempty : sigs <> [marshal ctx script]-completeWitnessSig _ input _ = input--collectSigs :: Int -> [PublicKey] -> Input -> [ByteString]-collectSigs m pubKeys input =-  take m . reverse $ foldl' lookupKey [] pubKeys-  where-    lookupKey sigs key =-      maybe sigs (: sigs) $-        HashMap.lookup key input.partialSigs---- | Take a finalized 'PSBT' and produce the signed final--- transaction. You may need to call 'complete' on the--- 'PSBT' before producing the final transaction.-finalTransaction :: PSBT -> Tx-finalTransaction psbt =-  setInputs-    . foldl' finalizeInput ([], [])-    $ zip tx.inputs psbt.inputs-  where-    tx = psbt.unsignedTransaction-    hasWitness = any (isJust . (.finalScriptWitness)) psbt.inputs-    setInputs (ins, witData) =-      tx-        { inputs = reverse ins,-          witness = if hasWitness then reverse witData else []-        }-    finalizeInput (ins, witData) (TxIn {..}, psbtInput) =-      ( TxIn-          { script =-              maybe-                mempty-                (runPutS . serialize)-                psbtInput.finalScriptSig,-            ..-          }-          : ins,-        fromMaybe [] psbtInput.finalScriptWitness : witData-      )---- | Take an unsigned transaction and produce an empty--- 'PSBT'-emptyPSBT :: Tx -> PSBT-emptyPSBT tx =-  PSBT-    { unsignedTransaction = tx,-      globalUnknown = mempty,-      inputs = replicate (length tx.inputs) emptyInput,-      outputs = replicate (length tx.outputs) emptyOutput-    }--emptyInput :: Input-emptyInput =-  Input-    Nothing-    Nothing-    HashMap.empty-    Nothing-    Nothing-    Nothing-    HashMap.empty-    Nothing-    Nothing-    (UnknownMap HashMap.empty)--emptyOutput :: Output-emptyOutput = Output Nothing Nothing HashMap.empty (UnknownMap HashMap.empty)--getPSBT :: (MonadGet m) => Ctx -> m PSBT-getPSBT ctx = do-  magic <- getBytes 4-  unless (magic == "psbt") $-    fail $-      "Expected magic = 'psbt' but got '" ++ cs magic ++ "'"-  headerSep <- getWord8-  unless (headerSep == 0xff) $-    fail $-      "Expected headerSep = 0xff but got 0x" ++ showHex headerSep ""--  keySize <- getWord8-  unless (keySize == 1) $-    fail $-      "Expected keySize = 1 but got " ++ show keySize-  globalUnsignedTxType <- getWord8-  unless (globalUnsignedTxType == 0x00) $-    fail $-      "Expected globalUnsignedTxType = 0x00 but got 0x"-        ++ showHex globalUnsignedTxType ""-  unsignedTransaction <- getSizedBytes deserialize-  unless (all (B.null . (.script)) unsignedTransaction.inputs) $-    fail $-      "Not all inputs from unsignedTransaction have empty scripts"-  unless (null unsignedTransaction.witness) $-    fail $-      "Not all witnesses from unsignedTransaction are empty"--  globalUnknown <- deserialize-  globalEnd <- getWord8-  unless (globalEnd == 0x00) $-    fail $-      "Expected globalEnd == 0x00 but got 0x" ++ showHex globalEnd ""--  inputs <--    replicateM-      (length unsignedTransaction.inputs)-      (getInput ctx)-  outputs <--    replicateM-      (length unsignedTransaction.outputs)-      (getOutput ctx)--  return-    PSBT-      { unsignedTransaction,-        globalUnknown,-        inputs,-        outputs-      }--putPSBT :: (MonadPut m) => Ctx -> PSBT -> m ()-putPSBT-  ctx-  PSBT-    { unsignedTransaction,-      globalUnknown,-      inputs,-      outputs-    } = do-    putByteString "psbt"-    putWord8 0xff -- Header separator-    putWord8 0x01 -- Key size-    putWord8 0x00 -- Unsigned Transaction type-    putSizedBytes $ S.encode unsignedTransaction-    serialize globalUnknown-    putWord8 0x00 -- Global end-    mapM_ (putInput ctx) inputs-    mapM_ (putOutput ctx) outputs--instance Serial Key where-  deserialize = do-    VarInt keySize <- deserialize-    unless (keySize > 0) $-      fail $-        "Expected keySize > 0 but got " ++ show keySize-    t <- getWord8-    k <- getBytes (fromIntegral keySize - 1)-    return (Key t k)--  serialize (Key t k) = do-    putVarInt $ 1 + B.length k-    putWord8 t-    putByteString k--instance Binary Key where-  put = serialize-  get = deserialize--instance Serialize Key where-  put = serialize-  get = deserialize--instance Serial UnknownMap where-  deserialize = go HashMap.empty-    where-      getItem m = do-        k <- deserialize-        VarString v <- deserialize-        go $ HashMap.insert k v m-      go m = do-        isEnd <- lookAhead getWord8-        if isEnd == 0x00-          then return (UnknownMap m)-          else getItem m--  serialize (UnknownMap m) =-    void $-      HashMap.traverseWithKey-        (\k v -> serialize k >> serialize (VarString v))-        m--getInput :: (MonadGet m) => Ctx -> m Input-getInput ctx =-  getMap (getInputItem ctx) setInputUnknown emptyInput-  where-    setInputUnknown f input =-      input-        { inputUnknown =-            UnknownMap (f input.inputUnknown.unknownMap)-        }--putInput :: (MonadPut m) => Ctx -> Input -> m ()-putInput-  ctx-  Input-    { nonWitnessUtxo,-      witnessUtxo,-      partialSigs,-      sigHashType,-      inputRedeemScript,-      inputWitnessScript,-      inputHDKeypaths,-      finalScriptSig,-      finalScriptWitness,-      inputUnknown-    } = do-    whenJust-      (putKeyValue InNonWitnessUtxo . S.encode)-      nonWitnessUtxo-    whenJust-      (putKeyValue InWitnessUtxo . S.encode)-      witnessUtxo-    putPartialSig partialSigs-    whenJust-      putSigHash-      sigHashType-    whenJust-      (putKeyValue InRedeemScript . S.encode)-      inputRedeemScript-    whenJust-      (putKeyValue InWitnessScript . S.encode)-      inputWitnessScript-    putHDPath ctx InBIP32Derivation inputHDKeypaths-    whenJust-      (putKeyValue InFinalScriptSig . S.encode)-      finalScriptSig-    whenJust-      (putKeyValue InFinalScriptWitness . putFinalScriptWitness)-      finalScriptWitness-    serialize inputUnknown-    putWord8 0x00-    where-      putPartialSig =-        putPubKeyMap ctx serialize InPartialSig . fmap VarString-      putSigHash sigHash = do-        putKey InSigHashType-        putWord8 0x04-        putWord32le (fromIntegral sigHash)-      putFinalScriptWitness witnessStack = runPutS $ do-        serialize $ (VarInt . fromIntegral . length) witnessStack-        mapM_ (serialize . VarString) witnessStack--getOutput :: (MonadGet m) => Ctx -> m Output-getOutput ctx = getMap (getOutputItem ctx) setOutputUnknown emptyOutput-  where-    setOutputUnknown f output =-      output-        { outputUnknown =-            UnknownMap (f output.outputUnknown.unknownMap)-        }--putOutput :: (MonadPut m) => Ctx -> Output -> m ()-putOutput-  ctx-  Output-    { outputRedeemScript,-      outputWitnessScript,-      outputHDKeypaths,-      outputUnknown-    } = do-    whenJust-      (putKeyValue OutRedeemScript . S.encode)-      outputRedeemScript-    whenJust-      (putKeyValue OutWitnessScript . S.encode)-      outputWitnessScript-    putHDPath-      ctx-      OutBIP32Derivation-      outputHDKeypaths-    serialize outputUnknown-    putWord8 0x00--putSizedBytes :: (MonadPut m) => ByteString -> m ()-putSizedBytes bs = do-  putVarInt (B.length bs)-  putByteString bs--getSizedBytes :: (MonadGet m) => Get a -> m a-getSizedBytes =-  getNested $ (\(VarInt i) -> fromIntegral i) <$> deserialize--putKeyValue :: (Enum t, MonadPut m) => t -> ByteString -> m ()-putKeyValue t v = do-  putKey t-  putSizedBytes v--putKey :: (Enum t, MonadPut m) => t -> m ()-putKey t = do-  putVarInt (1 :: Word8)-  putWord8 (enumWord8 t)--getMap ::-  (Bounded t, Enum t, MonadGet m) =>-  (Int -> a -> t -> m a) ->-  ((HashMap Key ByteString -> HashMap Key ByteString) -> a -> a) ->-  a ->-  m a-getMap getMapItem setUnknown = go-  where-    getItem keySize m (Right t) =-      getMapItem (fromIntegral keySize - 1) m t >>= go-    getItem keySize m (Left t) = do-      k <- getBytes (fromIntegral keySize - 1)-      VarString v <- deserialize-      go $ setUnknown (HashMap.insert (Key t k) v) m-    go m = do-      keySize <- (\(VarInt i) -> i) <$> deserialize-      if keySize == 0-        then return m-        else getItem keySize m . word8Enum =<< getWord8--data InputType-  = InNonWitnessUtxo-  | InWitnessUtxo-  | InPartialSig-  | InSigHashType-  | InRedeemScript-  | InWitnessScript-  | InBIP32Derivation-  | InFinalScriptSig-  | InFinalScriptWitness-  deriving (Show, Eq, Enum, Bounded, Generic)-  deriving anyclass (NFData)--data OutputType-  = OutRedeemScript-  | OutWitnessScript-  | OutBIP32Derivation-  deriving (Show, Eq, Enum, Bounded, Generic)-  deriving anyclass (NFData)--getInputItem ::-  (MonadGet m) =>-  Ctx ->-  Int ->-  Input ->-  InputType ->-  m Input-getInputItem ctx 0 input@Input {nonWitnessUtxo = Nothing} InNonWitnessUtxo = do-  utxo <- getSizedBytes deserialize-  return input {nonWitnessUtxo = Just utxo}-getInputItem ctx 0 input@Input {witnessUtxo = Nothing} InWitnessUtxo = do-  utxo <- getSizedBytes deserialize-  return input {witnessUtxo = Just utxo}-getInputItem ctx keySize input InPartialSig = do-  (k, v) <- getPartialSig-  return-    input-      { partialSigs = HashMap.insert k v input.partialSigs-      }-  where-    getPartialSig =-      (,)-        <$> isolate keySize (marshalGet ctx :: Get PublicKey)-        <*> ((\(VarString s) -> s) <$> deserialize)-getInputItem ctx 0 input@Input {sigHashType = Nothing} InSigHashType = do-  VarInt size <- deserialize-  unless (size == 0x04) $-    fail $-      "Expected size == 0x04 but got 0x" ++ showHex size ""-  sigHash <- fromIntegral <$> getWord32le-  return $ input {sigHashType = Just sigHash}-getInputItem ctx 0 input@Input {inputRedeemScript = Nothing} InRedeemScript = do-  script <- getSizedBytes deserialize-  return $ input {inputRedeemScript = Just script}-getInputItem ctx 0 input@Input {inputWitnessScript = Nothing} InWitnessScript = do-  script <- getSizedBytes deserialize-  return $ input {inputWitnessScript = Just script}-getInputItem ctx keySize input InBIP32Derivation = do-  (k, v) <- getHDPath ctx keySize-  return-    input-      { inputHDKeypaths = HashMap.insert k v input.inputHDKeypaths-      }-getInputItem ctx 0 input@Input {finalScriptSig = Nothing} InFinalScriptSig = do-  script <- getSizedBytes deserialize-  return $ input {finalScriptSig = Just script}-getInputItem ctx 0 input@Input {finalScriptWitness = Nothing} InFinalScriptWitness = do-  scripts <- map (\(VarString s) -> s) <$> getVarIntList-  return $ input {finalScriptWitness = Just scripts}-  where-    getVarIntList = getSizedBytes $ do-      VarInt n <- deserialize -- Item count-      replicateM (fromIntegral n) deserialize-getInputItem ctx keySize input inputType =-  fail "Incorrect key size for input item or item already existed"--getOutputItem :: (MonadGet m) => Ctx -> Int -> Output -> OutputType -> m Output-getOutputItem ctx 0 output@Output {outputRedeemScript = Nothing} OutRedeemScript = do-  script <- getSizedBytes deserialize-  return $ output {outputRedeemScript = Just script}-getOutputItem ctx 0 output@Output {outputWitnessScript = Nothing} OutWitnessScript = do-  script <- getSizedBytes deserialize-  return $ output {outputWitnessScript = Just script}-getOutputItem ctx keySize output OutBIP32Derivation = do-  (k, v) <- getHDPath ctx keySize-  return $ output {outputHDKeypaths = HashMap.insert k v output.outputHDKeypaths}-getOutputItem ctx keySize output outputType =-  fail "Incorrect key size for output item or item already existed"--getHDPath ::-  forall m.-  (MonadGet m) =>-  Ctx ->-  Int ->-  m (PublicKey, (Fingerprint, [KeyIndex]))-getHDPath ctx keySize =-  (,)-    <$> isolate keySize (marshalGet ctx :: Get PublicKey)-    <*> ((\(PSBTHDPath s) -> s) <$> deserialize)--putHDPath ::-  (Enum t, MonadPut m) =>-  Ctx ->-  t ->-  HashMap PublicKey (Fingerprint, [KeyIndex]) ->-  m ()-putHDPath ctx t = putPubKeyMap ctx serialize t . fmap PSBTHDPath--newtype PSBTHDPath = PSBTHDPath {unPSBTHDPath :: (Fingerprint, [KeyIndex])}-  deriving (Show, Eq, Generic)-  deriving newtype (NFData)--instance Serial PSBTHDPath where-  deserialize = do-    VarInt valueSize <- deserialize-    unless (valueSize `mod` 4 == 0) $-      fail $-        "Expected valueSize = 4 but got " ++ show valueSize-    let numIndices = (fromIntegral valueSize - 4) `div` 4-    PSBTHDPath-      <$> isolate-        (fromIntegral valueSize)-        ((,) <$> deserialize <*> getKeyIndexList numIndices)-    where-      getKeyIndexList n = replicateM n getWord32le--  serialize (PSBTHDPath (fp, kis)) = do-    putVarInt (B.length bs)-    putByteString bs-    where-      bs = runPutS $ serialize fp >> mapM_ putWord32le kis--instance Binary PSBTHDPath where-  put = serialize-  get = deserialize--instance Serialize PSBTHDPath where-  put = serialize-  get = deserialize--putPubKeyMap ::-  (Enum t, MonadPut m) =>-  Ctx ->-  (a -> m ()) ->-  t ->-  HashMap PublicKey a ->-  m ()-putPubKeyMap ctx f t =-  void . HashMap.traverseWithKey putItem-  where-    putItem k v = do-      serialize $ Key (enumWord8 t) (marshal ctx k)-      f v--enumWord8 :: (Enum a) => a -> Word8-enumWord8 = fromIntegral . fromEnum--word8Enum :: forall a. (Bounded a, Enum a) => Word8 -> Either Word8 a-word8Enum n | n <= enumWord8 (maxBound :: a) = Right . toEnum $ fromIntegral n-word8Enum n = Left n--whenJust :: (Monad m) => (a -> m ()) -> Maybe a -> m ()-whenJust = maybe (return ())--justWhen :: (a -> Bool) -> a -> Maybe a-justWhen test x = if test x then Just x else Nothing--isolate :: (MonadGet m) => Int -> Get a -> m a-isolate length getVal = do-  bs <- getByteString length-  either fail return (runGetS getVal bs)--getNested :: (MonadGet m) => m Int -> Get a -> m a-getNested getLength getVal = do-  length <- getLength-  isolate length getVal
+ src/Haskoin/Transaction/Psbt.hs view
@@ -0,0 +1,1903 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE NoFieldSelectors #-}++-- |+-- Module      : Haskoin.Transaction.Psbt+-- Copyright   : No rights reserved+-- License     : MIT+-- Maintainer  : jprupp@protonmail.ch+-- Stability   : experimental+-- Portability : POSIX+--+-- Partially signed Bitcoin transactions, the format of+-- [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki),+-- in its version 0 and in version 2, the version of+-- [BIP 370](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki).+--+-- A PSBT is five magic bytes, then maps of keys and values: one global map,+-- then one map per input and one per output of the transaction. A key is a+-- type and some data. The spec gives the keys and values of the fields it+-- defines, and leaves the type 0xFC to proprietary fields, under an+-- identifier of the software that writes them.+--+-- In version 0, the global map holds the unsigned transaction. In version+-- 2, the maps hold the transaction field by field: its version and a+-- fallback lock time in the global map, the output each input spends and+-- its sequence in the map of the input, the amount and script of each+-- output in its map, so that inputs and outputs can be added. The lock+-- time is the one the inputs require, or the fallback. Here a PSBT of+-- either version has its unsigned transaction, 'unsignedTx': the one of+-- the global map, or the one the fields describe.+--+-- The maps are kept as they are read, byte for byte, so that every field+-- passes through, known or not. A map is written with its keys in order, as+-- Bitcoin Core writes it. A PSBT that breaks the spec is refused when it is+-- read: a key out of shape, a key twice in a map, a value that does not+-- decode, a field of the other version, a field version 2 requires+-- missing, bytes after the last map.+--+-- The fields the spec defines are read and written with the functions+-- below, with the Taproot fields of+-- [BIP 371](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki).+-- The roles of the spec are here too: a creator ('psbtOf'), a+-- constructor ('addInput', 'addOutput'), a combiner ('combinePsbt'), a+-- signer ('signPsbt'), a finalizer ('finalizePsbt') and a transaction+-- extractor ('psbtTransaction'). An updater is the functions that insert+-- fields, and 'setInputSequence'.+module Haskoin.Transaction.Psbt+  ( -- * A PSBT+    Psbt (..),+    PsbtVersion (..),+    Key (..),+    PsbtMap,+    psbtOf,+    psbtOfMaps,+    withVersion,+    psbtId,+    combinePsbt,+    psbtTransaction,++    -- * The inputs and outputs of version 2+    addInput,+    addOutput,+    setInputSequence,+    Modifiable (..),+    txModifiable,+    insertTxModifiable,+    deleteTxModifiable,+    fallbackLockTime,+    requiredTimeLockTime,+    insertRequiredTimeLockTime,+    requiredHeightLockTime,+    insertRequiredHeightLockTime,++    -- * Bytes and text+    psbtMagic,+    isPsbt,+    encodePsbt,+    decodePsbt,+    encodePsbtBase64,+    decodePsbtBase64,++    -- * The global fields+    Derivation (..),+    xpubRecords,+    insertXpubRecord,++    -- * The fields of an input+    nonWitnessUtxo,+    insertNonWitnessUtxo,+    witnessUtxo,+    insertWitnessUtxo,+    partialSignatures,+    hasPartialSignatures,+    insertPartialSignature,+    deletePartialSignatures,+    sigHashType,+    insertSigHashType,+    deleteSigHashType,+    inputRedeemScript,+    insertInputRedeemScript,+    inputWitnessScript,+    insertInputWitnessScript,+    inputDerivations,+    insertInputDerivation,+    finalScriptSig,+    insertFinalScriptSig,+    finalScriptWitness,+    insertFinalScriptWitness,++    -- * The Taproot fields of an input+    tapKeySig,+    insertTapKeySig,+    tapScriptSigs,+    insertTapScriptSig,+    TapLeafScript (..),+    tapLeafScripts,+    insertTapLeafScript,+    TapDerivation (..),+    inputTapDerivations,+    insertInputTapDerivation,+    inputTapInternalKey,+    insertInputTapInternalKey,+    inputTapMerkleRoot,+    insertInputTapMerkleRoot,++    -- * The fields of an output+    outputRedeemScript,+    insertOutputRedeemScript,+    outputWitnessScript,+    insertOutputWitnessScript,+    outputDerivations,+    insertOutputDerivation,+    outputTapInternalKey,+    insertOutputTapInternalKey,+    outputTapTree,+    insertOutputTapTree,+    outputTapDerivations,+    insertOutputTapDerivation,++    -- * Proprietary fields+    Proprietary (..),+    proprietaryKey,+    proprietaryOf,+    lookupProprietary,+    insertProprietary,++    -- * Signing+    Signer (..),+    defaultSigHashes,+    keySigner,+    xPrvSigner,+    signPsbt,+    afterSignature,++    -- * Finalizing+    finalizePsbt,+  )+where++import Control.DeepSeq (NFData)+import Control.Monad (ap, forM, forM_, guard, replicateM, unless, when, zipWithM)+import Crypto.Hash (Digest, SHA256, digestFromByteString)+import Crypto.Secp256k1 (Ctx, PubKey, SecKey, derivePubKey, exportXOnlyPubKey, importXOnlyPubKey, signSchnorr, verifySchnorr)+import Data.Bifunctor (first)+import Data.Bits (complement, shiftL, testBit, (.&.), (.|.))+import Data.ByteArray qualified as BA+import Data.ByteString (ByteString)+import Data.ByteString qualified as B+import Data.Base64.Types (extractBase64)+import Data.ByteString.Base64 (decodeBase64Untyped, encodeBase64')+import Data.Char (toUpper)+import Data.List (find, nubBy, stripPrefix)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe, mapMaybe, maybeToList)+import Data.Serialize qualified as S+import Data.Word (Word32, Word64, Word8)+import GHC.Generics (Generic)+import Haskoin.Crypto.Hash (Hash160, addressHash)+import Haskoin.Crypto.Keys.Common+import Haskoin.Crypto.Keys.Extended+import Haskoin.Crypto.Signature (hashToMsg, verifyHashSig)+import Haskoin.Network.Common (VarInt (..))+import Haskoin.Network.Data+import Haskoin.Script.Common+import Haskoin.Script.SigHash+import Haskoin.Script.Standard+import Haskoin.Transaction.Builder.Sign (SigInput (..), makeSigHash, makeSignature)+import Haskoin.Transaction.Common+import Haskoin.Transaction.Segwit (isSegwit)+import Haskoin.Transaction.Taproot+import Haskoin.Util+import Numeric (showHex)++{- A PSBT -}++-- | A key of a map: its type, and the data after the type byte. Most+-- fields have none; a derivation or a signature has the public key it is+-- of.+data Key = Key+  { keyType :: !Word8,+    keyData :: !ByteString+  }+  deriving (Eq, Ord, Show, Read, Generic)+  deriving anyclass (NFData)++-- | A map of a PSBT. It is written with its keys in order, the type first,+-- as Bitcoin Core writes it.+type PsbtMap = Map Key ByteString++-- | The version of a PSBT: 0, the version of BIP 174, whose global map+-- holds the unsigned transaction, or 2, the version of BIP 370, whose maps+-- hold the transaction field by field, so that inputs and outputs can be+-- added. There is no version 1.+data PsbtVersion = Version0 | Version2+  deriving (Eq, Ord, Show, Read, Generic)+  deriving anyclass (NFData)++versionNumber :: PsbtVersion -> Word32+versionNumber Version0 = 0+versionNumber Version2 = 2++-- | A PSBT: its version, the unsigned transaction, the global map, and the+-- maps of the inputs and of the outputs, one each, in the order of the+-- transaction. In version 0, the global map is kept without the unsigned+-- transaction. In version 2, the maps keep the fields that describe the+-- transaction, and the unsigned transaction is the one they describe, with+-- the lock time they determine.+data Psbt = Psbt+  { version :: !PsbtVersion,+    unsignedTx :: !Tx,+    globals :: !PsbtMap,+    inputs :: ![PsbtMap],+    outputs :: ![PsbtMap]+  }+  deriving (Eq, Show, Read, Generic)+  deriving anyclass (NFData)++-- | The PSBT of a transaction, with nothing else, as the creator of the+-- spec makes it, in version 0: the transaction without its input scripts+-- and witness, and an empty map per input and per output. 'withVersion'+-- makes it one of version 2.+psbtOf :: Tx -> Psbt+psbtOf tx =+  Psbt+    { version = Version0,+      unsignedTx = Tx tx.version [TxIn input.outpoint B.empty input.sequence | input <- tx.inputs] tx.outputs [] tx.locktime,+      globals = Map.empty,+      inputs = Map.empty <$ tx.inputs,+      outputs = Map.empty <$ tx.outputs+    }++-- | The PSBT whose maps these are, as they are in its bytes: the global+-- map names the version, and holds the unsigned transaction in version 0.+-- The maps are checked against the spec of the version, and in version 2+-- the transaction is read from their fields.+psbtOfMaps :: PsbtMap -> [PsbtMap] -> [PsbtMap] -> Either String Psbt+psbtOfMaps globals inputs outputs = do+  (version, inputCount, outputCount) <- countsOf globals+  unless (length inputs == inputCount) $+    Left ("The PSBT has " <> show (length inputs) <> " inputs, and its global map counts " <> show inputCount)+  unless (length outputs == outputCount) $+    Left ("The PSBT has " <> show (length outputs) <> " outputs, and its global map counts " <> show outputCount)+  forM_ (zip [0 :: Int ..] inputs) $ \(i, m) -> checkMap ("input " <> show i) version inputFields m+  forM_ (zip [0 :: Int ..] outputs) $ \(i, m) -> checkMap ("output " <> show i) version outputFields m+  tx <- case version of+    Version0 -> unsignedTxOf globals+    Version2 -> transactionOf globals inputs outputs+  return $ Psbt version tx (Map.delete unsignedTxKey globals) inputs outputs++-- | The PSBT in the version given, as BIP 370 converts one. To version 2:+-- the transaction goes in the fields of the maps, its lock time as the+-- fallback. To version 0: the fields of version 2 leave the maps, and the+-- transaction keeps the lock time they determined.+withVersion :: PsbtVersion -> Psbt -> Psbt+withVersion v psbt+  | v == psbt.version = psbt+  | v == Version2 =+      Psbt+        Version2+        tx+        (withTxFields tx (insertPlainField versionType (word32le 2) psbt.globals))+        (zipWith withInputFields tx.inputs psbt.inputs)+        (zipWith withOutputFields tx.outputs psbt.outputs)+  | otherwise =+      Psbt+        Version0+        tx+        (without globalV2Types (deletePlainField versionType psbt.globals))+        (without inputV2Types <$> psbt.inputs)+        (without outputV2Types <$> psbt.outputs)+  where+    tx = psbt.unsignedTx+    without :: [Word8] -> PsbtMap -> PsbtMap+    without types = Map.filterWithKey (\key _ -> key.keyType `notElem` types)++-- | What identifies a PSBT: the id of its unsigned transaction. In version+-- 2 the sequence of every input is taken as 0 for it, as BIP 370 says,+-- since an updater may change the sequences.+psbtId :: Psbt -> TxHash+psbtId psbt = txHash $ case psbt.version of+  Version0 -> tx+  Version2 -> Tx tx.version [TxIn input.outpoint input.script 0 | input <- tx.inputs] tx.outputs tx.witness tx.locktime+  where+    tx = psbt.unsignedTx++-- | The PSBT with the fields of both, as the combiner of the spec makes+-- it. Both must be of the same version and of the same transaction. A+-- field both have, with different values, keeps the value of the first.+-- The flags of version 2 are combined as Bitcoin Core combines them:+-- inputs and outputs stay modifiable only when both say so, and a+-- SIGHASH_SINGLE signature of either is kept.+combinePsbt :: Psbt -> Psbt -> Either String Psbt+combinePsbt a b = do+  unless (a.version == b.version) $ Left "The PSBTs are not of the same version"+  unless (psbtId a == psbtId b) $ Left "The PSBTs are not of the same transaction"+  let globals = combinedFlags (Map.union a.globals b.globals)+      inputs = zipWith Map.union a.inputs b.inputs+      outputs = zipWith Map.union a.outputs b.outputs+  case a.version of+    Version0 -> return $ Psbt Version0 a.unsignedTx globals inputs outputs+    Version2 -> (\tx -> Psbt Version2 tx globals inputs outputs) <$> transactionOf globals inputs outputs+  where+    combinedFlags globals = case (flagsOf a, flagsOf b) of+      (Nothing, Nothing) -> globals+      (x, y) ->+        let fx = fromMaybe noFlags x+            fy = fromMaybe noFlags y+         in insertTxModifiable+              (Modifiable (fx.inputsModifiable && fy.inputsModifiable) (fx.outputsModifiable && fy.outputsModifiable) (fx.hasSigHashSingle || fy.hasSigHashSingle) (fx.undefinedBits .&. fy.undefinedBits))+              globals+    flagsOf :: Psbt -> Maybe Modifiable+    flagsOf psbt = either (const Nothing) id (txModifiable psbt.globals)++-- | The transaction of a PSBT with the final script and witness of every+-- input that has them, as the transaction extractor of the spec builds+-- it. An input without them stays unsigned.+psbtTransaction :: Psbt -> Either String Tx+psbtTransaction psbt = do+  finals <- forM (zip psbt.unsignedTx.inputs psbt.inputs) $ \(input, m) -> do+    stack <- fromMaybe [] <$> finalScriptWitness m+    let scriptSig = fromMaybe B.empty (finalScriptSig m)+    return (TxIn input.outpoint scriptSig input.sequence, stack)+  let stacks = snd <$> finals+      witness = if all null stacks then [] else stacks+  return $ Tx psbt.unsignedTx.version (fst <$> finals) psbt.unsignedTx.outputs witness psbt.unsignedTx.locktime++{- The inputs and outputs of version 2 -}++-- | The PSBT with an input added at the end, as the constructor of BIP 370+-- adds one: the output it spends and its sequence from the input given,+-- and the fields of the map given, such as the lock time it requires. A+-- PSBT of version 0 takes none, nor one whose inputs are not modifiable.+-- A lock time the input requires must fit the other inputs, and must not+-- change the lock time of the transaction once an input is signed. Added+-- at the end, the input leaves every SIGHASH_SINGLE signature paired with+-- its output.+addInput :: TxIn -> PsbtMap -> Psbt -> Either String Psbt+addInput input m psbt = do+  flags <- modifiableOf psbt+  unless flags.inputsModifiable $ Left "The inputs of the PSBT may not be modified"+  when (any ((== input.outpoint) . (.outpoint)) psbt.unsignedTx.inputs) $+    Left "An input of the PSBT spends that output already"+  let inputs = psbt.inputs <> [withInputFields input m]+      globals = withCounts (length inputs) (length psbt.outputs) psbt.globals+  tx <- transactionOf globals inputs psbt.outputs+  when (any hasSignatures psbt.inputs && tx.locktime /= psbt.unsignedTx.locktime) $+    Left "An input is signed already: the lock time may not change"+  return $ Psbt Version2 tx globals inputs psbt.outputs++-- | The PSBT with an output added at the end, as the constructor of BIP+-- 370 adds one: its amount and script from the output given, and the+-- fields of the map given. A PSBT of version 0 takes none, nor one whose+-- outputs are not modifiable.+addOutput :: TxOut -> PsbtMap -> Psbt -> Either String Psbt+addOutput output m psbt = do+  flags <- modifiableOf psbt+  unless flags.outputsModifiable $ Left "The outputs of the PSBT may not be modified"+  let outputs = psbt.outputs <> [withOutputFields output m]+      globals = withCounts (length psbt.inputs) (length outputs) psbt.globals+  tx <- transactionOf globals psbt.inputs outputs+  return $ Psbt Version2 tx globals psbt.inputs outputs++-- | The flags of a PSBT of version 2, none set when it has no field of+-- them. A PSBT of version 0 takes no input or output.+modifiableOf :: Psbt -> Either String Modifiable+modifiableOf psbt = do+  unless (psbt.version == Version2) $ Left "A PSBT of version 0 takes no input or output: its transaction is fixed"+  fromMaybe noFlags <$> txModifiable psbt.globals++-- | Whether an input has a signature: one that is not final, a Taproot+-- one, or its final script or witness+hasSignatures :: PsbtMap -> Bool+hasSignatures m = hasPartialSignatures m || isFinal m || any ((`elem` [tapKeySigType, tapScriptSigType]) . (.keyType)) (Map.keys m)++-- | The PSBT with the sequence of the input at the index set, as an+-- updater of BIP 370 may set one. The sequences of a PSBT of version 0+-- are in its unsigned transaction, which is fixed. An input that is+-- signed keeps its sequence, since every signature commits to it.+setInputSequence :: Int -> Word32 -> Psbt -> Either String Psbt+setInputSequence i sequence' psbt = do+  unless (psbt.version == Version2) $ Left "The sequences of a PSBT of version 0 are in its unsigned transaction, which is fixed"+  unless (i >= 0 && i < length psbt.inputs) $ Left ("The PSBT has no input " <> show i)+  when (hasSignatures (psbt.inputs !! i)) $ Left ("Input " <> show i <> " is signed already: its sequence may not change")+  let inputs = [if j == i then insertPlainField sequenceType (word32le sequence') m else m | (j, m) <- zip [0 ..] psbt.inputs]+  tx <- transactionOf psbt.globals inputs psbt.outputs+  return $ Psbt Version2 tx psbt.globals inputs psbt.outputs++-- | The global map with the fields of version 2 that describe the+-- transaction: its version, its lock time as the fallback, and the number+-- of its inputs and outputs+withTxFields :: Tx -> PsbtMap -> PsbtMap+withTxFields tx =+  insertPlainField txVersionType (word32le tx.version)+    . insertPlainField fallbackLockTimeType (word32le tx.locktime)+    . withCounts (length tx.inputs) (length tx.outputs)++withCounts :: Int -> Int -> PsbtMap -> PsbtMap+withCounts inputCount outputCount =+  insertPlainField inputCountType (compactSize (fromIntegral inputCount))+    . insertPlainField outputCountType (compactSize (fromIntegral outputCount))++-- | The map of an input with the fields of version 2 that describe it: the+-- id of the transaction it spends from, the index of the output, and its+-- sequence+withInputFields :: TxIn -> PsbtMap -> PsbtMap+withInputFields input =+  insertPlainField prevTxidType (S.encode input.outpoint.hash)+    . insertPlainField outputIndexType (word32le input.outpoint.index)+    . insertPlainField sequenceType (word32le input.sequence)++-- | The map of an output with the fields of version 2 that describe it:+-- its amount and its script+withOutputFields :: TxOut -> PsbtMap -> PsbtMap+withOutputFields output =+  insertPlainField amountType (word64le output.value)+    . insertPlainField outScriptType output.script++{- The types of the spec -}++unsignedTxType, xpubType, txVersionType, fallbackLockTimeType, inputCountType, outputCountType, txModifiableType, versionType :: Word8+unsignedTxType = 0x00+xpubType = 0x01+txVersionType = 0x02+fallbackLockTimeType = 0x03+inputCountType = 0x04+outputCountType = 0x05+txModifiableType = 0x06+versionType = 0xfb++nonWitnessUtxoType, witnessUtxoType, partialSigType, sigHashTypeType, inRedeemScriptType, inWitnessScriptType, inDerivationType, finalScriptSigType, finalScriptWitnessType, tapKeySigType, tapScriptSigType, tapLeafScriptType, inTapDerivationType, inTapInternalKeyType, tapMerkleRootType :: Word8+nonWitnessUtxoType = 0x00+witnessUtxoType = 0x01+partialSigType = 0x02+sigHashTypeType = 0x03+inRedeemScriptType = 0x04+inWitnessScriptType = 0x05+inDerivationType = 0x06+finalScriptSigType = 0x07+finalScriptWitnessType = 0x08+tapKeySigType = 0x13+tapScriptSigType = 0x14+tapLeafScriptType = 0x15+inTapDerivationType = 0x16+inTapInternalKeyType = 0x17+tapMerkleRootType = 0x18++prevTxidType, outputIndexType, sequenceType, requiredTimeType, requiredHeightType :: Word8+prevTxidType = 0x0e+outputIndexType = 0x0f+sequenceType = 0x10+requiredTimeType = 0x11+requiredHeightType = 0x12++outRedeemScriptType, outWitnessScriptType, outDerivationType, amountType, outScriptType, outTapInternalKeyType, outTapTreeType, outTapDerivationType :: Word8+outRedeemScriptType = 0x00+outWitnessScriptType = 0x01+outDerivationType = 0x02+amountType = 0x03+outScriptType = 0x04+outTapInternalKeyType = 0x05+outTapTreeType = 0x06+outTapDerivationType = 0x07++-- | The types of the fields of version 2, which version 0 excludes, by+-- map+globalV2Types, inputV2Types, outputV2Types :: [Word8]+globalV2Types = [txVersionType, fallbackLockTimeType, inputCountType, outputCountType, txModifiableType]+inputV2Types = [prevTxidType, outputIndexType, sequenceType, requiredTimeType, requiredHeightType]+outputV2Types = [amountType, outScriptType]++-- | The lock times from this value are times, below it heights+lockTimeThreshold :: Word32+lockTimeThreshold = 500000000++proprietaryType :: Word8+proprietaryType = 0xfc++-- | The key of the unsigned transaction in the global map+unsignedTxKey :: Key+unsignedTxKey = Key unsignedTxType B.empty++{- Bytes -}++-- | The five bytes every PSBT starts with: "psbt" and 0xff+psbtMagic :: ByteString+psbtMagic = B.pack [0x70, 0x73, 0x62, 0x74, 0xff]++-- | Whether the bytes are those of a PSBT, by their first five+isPsbt :: ByteString -> Bool+isPsbt = B.isPrefixOf psbtMagic++-- | The bytes of a PSBT: the magic, the global map, with the unsigned+-- transaction in version 0, then the maps of the inputs and of the outputs+encodePsbt :: Psbt -> ByteString+encodePsbt psbt =+  B.concat $ psbtMagic : encodeMap globals : (encodeMap <$> psbt.inputs) <> (encodeMap <$> psbt.outputs)+  where+    globals = case psbt.version of+      Version0 -> Map.insert unsignedTxKey (S.encode psbt.unsignedTx) psbt.globals+      Version2 -> psbt.globals++-- | A map: each key and value after its length, the keys in order, then a+-- zero byte+encodeMap :: PsbtMap -> ByteString+encodeMap m =+  B.concat [sized (B.cons key.keyType key.keyData) <> sized value | (key, value) <- Map.toAscList m]+    <> B.singleton 0++-- | Bytes after their length, as a compact size integer+sized :: ByteString -> ByteString+sized bytes = compactSize (fromIntegral (B.length bytes)) <> bytes++-- | A compact size integer, in its shortest form+compactSize :: Word64 -> ByteString+compactSize = S.encode . VarInt++-- | A 32-bit integer, little-endian+word32le :: Word32 -> ByteString+word32le = S.runPut . S.putWord32le++-- | A 64-bit integer, little-endian+word64le :: Word64 -> ByteString+word64le = S.runPut . S.putWord64le++-- | The PSBT the bytes hold, or why they hold none: not a PSBT, or one that+-- breaks the spec+decodePsbt :: ByteString -> Either String Psbt+decodePsbt = parseAll psbtParser "the last map"++-- | The text of a PSBT, in base64, as the spec gives it for text+encodePsbtBase64 :: Psbt -> ByteString+encodePsbtBase64 = extractBase64 . encodeBase64' . encodePsbt++-- | The PSBT of a base64 text, around which spaces and line breaks are+-- left aside+decodePsbtBase64 :: ByteString -> Either String Psbt+decodePsbtBase64 text = do+  bytes <- first (const "Not the base64 text of a PSBT") $ decodeBase64Untyped (B.filter (`notElem` spaces) text)+  decodePsbt bytes+  where+    spaces = [0x20, 0x09, 0x0a, 0x0d]++{- Reading bytes -}++-- | A reader of bytes, which stops at the first byte out of place with a+-- sentence saying so+newtype BytesParser a = BytesParser (ByteString -> Either String (a, ByteString))++instance Functor BytesParser where+  fmap f (BytesParser p) = BytesParser $ \bytes -> do+    (a, rest) <- p bytes+    return (f a, rest)++instance Applicative BytesParser where+  pure a = BytesParser $ \bytes -> Right (a, bytes)+  (<*>) = ap++instance Monad BytesParser where+  BytesParser p >>= f = BytesParser $ \bytes -> do+    (a, rest) <- p bytes+    let BytesParser q = f a+    q rest++refuse :: String -> BytesParser a+refuse reason = BytesParser $ \_ -> Left reason++-- | Read all the bytes with the parser: nothing may follow what it reads+parseAll :: BytesParser a -> String -> ByteString -> Either String a+parseAll (BytesParser p) what bytes = do+  (a, rest) <- p bytes+  unless (B.null rest) $ Left ("Bytes follow " <> what)+  return a++-- | The next bytes, which must be there. What they are part of names+-- them in the refusal.+bytesOf :: Int -> String -> BytesParser ByteString+bytesOf n what = BytesParser $ \bytes ->+  if B.length bytes < n+    then Left ("The PSBT ends inside " <> what)+    else Right (B.splitAt n bytes)++-- | All the bytes left+restOf :: BytesParser ByteString+restOf = BytesParser $ \bytes -> Right (bytes, B.empty)++-- | A compact size integer: one byte below 0xfd, or 0xfd, 0xfe or 0xff and+-- two, four or eight bytes, little-endian, in the shortest form, as+-- Bitcoin Core reads it+compactSizeOf :: String -> BytesParser Word64+compactSizeOf what = do+  first' <- B.head <$> bytesOf 1 what+  case first' of+    0xfd -> longer 2 0xfd+    0xfe -> longer 4 0x10000+    0xff -> longer 8 0x100000000+    _ -> return (fromIntegral first')+  where+    longer :: Int -> Word64 -> BytesParser Word64+    longer n least = do+      value <- B.foldr (\b acc -> acc `shiftL` 8 .|. fromIntegral b) 0 <$> bytesOf n what+      when (value < least) $ refuse ("A length in " <> what <> " is not in its shortest form")+      return value++-- | A length, which must fit in memory+lengthOf :: String -> BytesParser Int+lengthOf what = do+  n <- compactSizeOf what+  when (n > fromIntegral (maxBound :: Int)) $ refuse ("A length in " <> what <> " is beyond any PSBT")+  return (fromIntegral n)++-- | Bytes after their length+sizedBytes :: String -> BytesParser ByteString+sizedBytes what = lengthOf what >>= (`bytesOf` what)++-- | A map: keys and values up to a zero byte, each key once+mapOf :: String -> BytesParser PsbtMap+mapOf what = go Map.empty+  where+    go m = do+      keyLength <- lengthOf what+      if keyLength == 0+        then return m+        else do+          keyBytes <- bytesOf keyLength what+          value <- sizedBytes what+          let key = Key (B.head keyBytes) (B.tail keyBytes)+          when (Map.member key m) $+            refuse (capital what <> " has the key " <> showKey key <> " twice")+          go (Map.insert key value m)++psbtParser :: BytesParser Psbt+psbtParser = do+  magic <- bytesOf 5 "the magic bytes"+  unless (magic == psbtMagic) $ refuse "Not a PSBT: the bytes do not start with the magic bytes of BIP 174"+  globals <- mapOf "the global map"+  (_, inputCount, outputCount) <- either refuse return (countsOf globals)+  inputs <- forM [0 .. inputCount - 1] $ \i -> mapOf ("input " <> show i)+  outputs <- forM [0 .. outputCount - 1] $ \i -> mapOf ("output " <> show i)+  either refuse return (psbtOfMaps globals inputs outputs)++-- | The version a global map names, and the number of inputs and of+-- outputs: those of the unsigned transaction in version 0, those the map+-- counts in version 2. The map is checked against the spec of the version+-- first.+countsOf :: PsbtMap -> Either String (PsbtVersion, Int, Int)+countsOf globals = do+  version <- versionOf globals+  checkMap "the global map" version globalFields globals+  case version of+    Version0 -> do+      tx <- unsignedTxOf globals+      return (Version0, length tx.inputs, length tx.outputs)+    Version2 -> do+      _ <- requiredField "The global map" "transaction version" txVersionType decodeWord32le globals+      inputCount <- requiredField "The global map" "input count" inputCountType decodeCount globals+      outputCount <- requiredField "The global map" "output count" outputCountType decodeCount globals+      return (Version2, inputCount, outputCount)++-- | The version the global map names with PSBT_GLOBAL_VERSION: 0 when it+-- names none+versionOf :: PsbtMap -> Either String PsbtVersion+versionOf globals = case plainField versionType globals of+  Nothing -> Right Version0+  Just bytes -> do+    n <- first ("The global map: the value of the key 0xfb " <>) (decodeWord32le bytes)+    case n of+      0 -> Right Version0+      2 -> Right Version2+      _ -> Left ("The PSBT is of version " <> show n <> ": only versions 0 and 2 are supported")++-- | The unsigned transaction of the global map of version 0, which must be+-- there, in the serialization without witness, with no input script. It+-- is read with 'parseLegacyTx', as Bitcoin Core reads it without allowing+-- a witness, so that a transaction with no inputs, which the spec allows,+-- is not taken for the extended format of BIP 144.+unsignedTxOf :: PsbtMap -> Either String Tx+unsignedTxOf globals = do+  bytes <- maybe (Left "The global map has no unsigned transaction") Right (Map.lookup unsignedTxKey globals)+  tx <- case S.runGetState parseLegacyTx bytes 0 of+    Right (parsed, rest) | B.null rest -> Right parsed+    _ -> Left "The unsigned transaction does not decode"+  unless (null tx.witness && S.encode tx == bytes) $+    Left "The unsigned transaction is not in the serialization without witness"+  unless (all (B.null . (.script)) tx.inputs) $+    Left "The unsigned transaction has an input with a script"+  return tx++-- | The transaction the fields of version 2 describe: its version from+-- the global map, the lock time the inputs require or the fallback, an+-- input per input map from the id of the transaction it spends from, the+-- index of the output and the sequence, final when the map has none, and+-- an output per output map from its amount and script+transactionOf :: PsbtMap -> [PsbtMap] -> [PsbtMap] -> Either String Tx+transactionOf globals inputs outputs = do+  version <- requiredField "The global map" "transaction version" txVersionType decodeWord32le globals+  locktime <- lockTimeOf globals inputs+  txInputs <- forM (zip [0 :: Int ..] inputs) $ \(i, m) -> do+    let place = "Input " <> show i+    hash <- requiredField place "previous transaction id" prevTxidType decodeTxHash m+    index <- requiredField place "output index" outputIndexType decodeWord32le m+    sequence' <- fromMaybe 0xffffffff <$> optionalField "The sequence" sequenceType decodeWord32le m+    return (TxIn (OutPoint hash index) B.empty sequence')+  txOutputs <- forM (zip [0 :: Int ..] outputs) $ \(i, m) -> do+    let place = "Output " <> show i+    value <- requiredField place "amount" amountType decodeWord64le m+    script <- requiredField place "script" outScriptType Right m+    return (TxOut value script)+  return (Tx version txInputs txOutputs [] locktime)++-- | The lock time of a PSBT of version 2, as BIP 370 determines it. An+-- input may require a lock time as a height, as a time, or either, with+-- both. When no input requires one, it is the fallback lock time of the+-- global map, 0 without one. Otherwise it is the greatest of the kind every+-- input that requires one takes, a height when every one takes a height.+-- When some inputs take a height only and others a time only, no lock time+-- fits them.+lockTimeOf :: PsbtMap -> [PsbtMap] -> Either String Word32+lockTimeOf globals inputs = do+  fallback <- fromMaybe 0 <$> fallbackLockTime globals+  required <- forM inputs $ \m -> (,) <$> requiredTimeLockTime m <*> requiredHeightLockTime m+  let asking = [r | r@(time, height) <- required, isJust time || isJust height]+      heights = [height | (_, Just height) <- asking]+      times = [time | (Just time, _) <- asking]+  case () of+    _+      | null asking -> Right fallback+      | length heights == length asking -> Right (maximum heights)+      | length times == length asking -> Right (maximum times)+      | otherwise -> Left "No lock time fits every input: some require a height, others a time"++-- | A field a map must have, decoded. Its absence, or a value out of shape,+-- names the map and the field.+requiredField :: String -> String -> Word8 -> (ByteString -> Either String a) -> PsbtMap -> Either String a+requiredField what name t decode m = do+  bytes <- maybe (Left (what <> " has no " <> name)) Right (plainField t m)+  first ((what <> ": the " <> name <> " ") <>) (decode bytes)++-- | A field a map may have, decoded, named in a refusal+optionalField :: String -> Word8 -> (ByteString -> Either String a) -> PsbtMap -> Either String (Maybe a)+optionalField name t decode = traverse (first ((name <> " ") <>) . decode) . plainField t++-- | A value decoded from all the bytes given, or a refusal naming it+decodeFully :: (S.Serialize a) => String -> ByteString -> Either String a+decodeFully what bytes = case S.runGetState S.get bytes 0 of+  Right (a, rest) | B.null rest -> Right a+  _ -> Left (what <> " does not decode")++-- | A sentence that starts with the words given+capital :: String -> String+capital (c : rest) = toUpper c : rest+capital [] = []++-- | A key as a refusal names it: its type in hexadecimal, and its data+showKey :: Key -> String+showKey key =+  "0x" <> hexByte key.keyType <> (if B.null key.keyData then "" else " with the data " <> concatMap hexByte (B.unpack key.keyData))+  where+    hexByte b = (if b < 16 then "0" else "") <> showHex b ""++{- The shapes of the spec -}++-- | The data of a key of the spec: none, a public key, an extended public+-- key, an x-only public key, an x-only public key with a leaf hash, or a+-- control block+data KeyShape = NoData | PublicKeyData | XpubData | XOnlyKeyData | KeyAndLeafData | ControlBlockData++-- | A field the spec defines: the shape of its key, the versions of the+-- PSBT that may hold it, and a check of its value given the data of the+-- key+data Field = Field+  { shape :: KeyShape,+    versions :: [PsbtVersion],+    checkValue :: ByteString -> ByteString -> Either String ()+  }++-- | The fields the spec defines for a map, by type+type Fields = Map Word8 Field++-- | A field of both versions whose key carries no data+plain :: (ByteString -> Either String ()) -> Field+plain = Field NoData [Version0, Version2] . const++-- | A field of version 2 only whose key carries no data+plain2 :: (ByteString -> Either String ()) -> Field+plain2 = Field NoData [Version2] . const++-- | Check every key of a map against the spec: the keys of the fields it+-- defines must be of the version, must have their shape and their values+-- must decode, and a proprietary key must have its identifier and subtype.+-- Other keys are not the spec's to check.+checkMap :: String -> PsbtVersion -> Fields -> PsbtMap -> Either String ()+checkMap what version fields m = forM_ (Map.toList m) $ \(key, value) ->+  first ((capital what <> ": ") <>) $+    case Map.lookup key.keyType fields of+      Just field -> do+        unless (version `elem` field.versions) $+          Left ("the key " <> showKey key <> " is not of PSBT version " <> show (versionNumber version))+        checkShape field.shape key+        first (("the value of the key " <> showKey key <> " ") <>) $ field.checkValue key.keyData value+      Nothing+        | key.keyType == proprietaryType -> () <$ proprietaryOf key+        | otherwise -> return ()++checkShape :: KeyShape -> Key -> Either String ()+checkShape shape key = case shape of+  NoData -> unless (B.null key.keyData) $ Left ("the key " <> showKey key <> " carries data")+  PublicKeyData -> unless (isPublicKeyBytes key.keyData) $ Left ("the key " <> showKey key <> " is not a public key")+  XpubData -> unless (B.length key.keyData == 78) $ Left ("the key " <> showKey key <> " is not an extended public key")+  XOnlyKeyData -> unless (B.length key.keyData == 32) $ Left ("the key " <> showKey key <> " is not an x-only public key")+  KeyAndLeafData -> unless (B.length key.keyData == 64) $ Left ("the key " <> showKey key <> " is not an x-only public key and a leaf hash")+  ControlBlockData -> unless (isControlBlockLength (B.length key.keyData)) $ Left ("the key " <> showKey key <> " is not a control block")++-- | Whether a control block may have the length: 33 bytes plus 32 per+-- node, with at most 'maxControlBlockNodes' nodes+isControlBlockLength :: Int -> Bool+isControlBlockLength len =+  len >= 33 && (len - 33) `mod` 32 == 0 && (len - 33) `div` 32 <= maxControlBlockNodes++-- | Whether the bytes have the length and the first byte of a public key,+-- compressed or not. Whether they are a point of the curve is checked+-- where the key is used.+isPublicKeyBytes :: ByteString -> Bool+isPublicKeyBytes bytes = case B.unpack (B.take 1 bytes) of+  [0x02] -> B.length bytes == 33+  [0x03] -> B.length bytes == 33+  [0x04] -> B.length bytes == 65+  _ -> False++-- The unsigned transaction is checked apart, by 'unsignedTxOf', and the+-- version by 'versionOf'+globalFields :: Fields+globalFields =+  Map.fromList+    [ (unsignedTxType, Field NoData [Version0] (const anyValue)),+      (xpubType, Field XpubData [Version0, Version2] xpubValue),+      (txVersionType, plain2 word32Value),+      (fallbackLockTimeType, plain2 word32Value),+      (inputCountType, plain2 countValue),+      (outputCountType, plain2 countValue),+      (txModifiableType, plain2 flagsValue),+      (versionType, plain word32Value)+    ]+  where+    -- The path of an extended key has one index per level of its depth,+    -- the fifth byte of the key+    xpubValue keyData value = do+      derivation <- decodeDerivation value+      unless (length (pathToList derivation.path) == fromIntegral (B.index keyData 4)) $+        Left "has a path that is not as deep as the key"+    countValue value = () <$ decodeCount value+    flagsValue value = unless (B.length value == 1) $ Left "is not a byte of flags"++inputFields :: Fields+inputFields =+  Map.fromList+    [ (nonWitnessUtxoType, plain txValue),+      (witnessUtxoType, plain txOutValue),+      (partialSigType, Field PublicKeyData [Version0, Version2] (const anyValue)),+      (sigHashTypeType, plain word32Value),+      (inRedeemScriptType, plain scriptValue),+      (inWitnessScriptType, plain scriptValue),+      (inDerivationType, Field PublicKeyData [Version0, Version2] (const derivationValue)),+      (finalScriptSigType, plain anyValue),+      (finalScriptWitnessType, plain witnessValue),+      (tapKeySigType, plain tapSigValue),+      (tapScriptSigType, Field KeyAndLeafData [Version0, Version2] (const tapSigValue)),+      (tapLeafScriptType, Field ControlBlockData [Version0, Version2] (const leafScriptValue)),+      (inTapDerivationType, Field XOnlyKeyData [Version0, Version2] (const tapDerivationValue)),+      (inTapInternalKeyType, plain xOnlyKeyValue),+      (tapMerkleRootType, plain hashValue),+      (prevTxidType, plain2 txHashValue),+      (outputIndexType, plain2 word32Value),+      (sequenceType, plain2 word32Value),+      (requiredTimeType, plain2 timeLockValue),+      (requiredHeightType, plain2 heightLockValue)+    ]+  where+    txValue value = () <$ (decodeTx value :: Either String Tx)+    txOutValue value = () <$ (decodeTxOut value :: Either String TxOut)+    witnessValue value = () <$ decodeWitness value+    txHashValue value = () <$ decodeTxHash value+    timeLockValue value = do+      time <- decodeWord32le value+      unless (time >= lockTimeThreshold) $ Left ("is a time lock below " <> show lockTimeThreshold)+    heightLockValue value = do+      height <- decodeWord32le value+      unless (height > 0 && height < lockTimeThreshold) $ Left ("is not a height between 1 and " <> show (lockTimeThreshold - 1))++outputFields :: Fields+outputFields =+  Map.fromList+    [ (outRedeemScriptType, plain scriptValue),+      (outWitnessScriptType, plain scriptValue),+      (outDerivationType, Field PublicKeyData [Version0, Version2] (const derivationValue)),+      (amountType, plain2 amountValue),+      (outScriptType, plain2 anyValue),+      (outTapInternalKeyType, plain xOnlyKeyValue),+      (outTapTreeType, plain tapTreeValue),+      (outTapDerivationType, Field XOnlyKeyData [Version0, Version2] (const tapDerivationValue))+    ]+  where+    amountValue value = () <$ decodeWord64le value++tapSigValue :: ByteString -> Either String ()+tapSigValue value = () <$ first ("is not a Taproot signature: " <>) (decodeTaprootSig value)++leafScriptValue :: ByteString -> Either String ()+leafScriptValue value = when (B.null value) $ Left "has no leaf version"++tapDerivationValue :: ByteString -> Either String ()+tapDerivationValue value = () <$ decodeTapDerivation value++xOnlyKeyValue :: ByteString -> Either String ()+xOnlyKeyValue value = unless (B.length value == 32) $ Left "is not an x-only public key"++hashValue :: ByteString -> Either String ()+hashValue value = unless (B.length value == 32) $ Left "is not a 32-byte hash"++tapTreeValue :: ByteString -> Either String ()+tapTreeValue value = () <$ decodeTapTree value++word32Value :: ByteString -> Either String ()+word32Value value = () <$ decodeWord32le value++anyValue :: ByteString -> Either String ()+anyValue _ = Right ()++derivationValue :: ByteString -> Either String ()+derivationValue value = () <$ decodeDerivation value++scriptValue :: ByteString -> Either String ()+scriptValue value = () <$ decodeScript value++decodeTx :: ByteString -> Either String Tx+decodeTx = decodeFully "is not a transaction: it"++decodeTxOut :: ByteString -> Either String TxOut+decodeTxOut = decodeFully "is not an output: it"++decodeScript :: ByteString -> Either String Script+decodeScript = decodeFully "is not a script: it"++decodeWord32le :: ByteString -> Either String Word32+decodeWord32le bytes+  | B.length bytes == 4 = Right $ B.foldr (\b acc -> acc `shiftL` 8 .|. fromIntegral b) 0 bytes+  | otherwise = Left "is not a 32-bit integer"++decodeWord64le :: ByteString -> Either String Word64+decodeWord64le bytes+  | B.length bytes == 8 = Right $ B.foldr (\b acc -> acc `shiftL` 8 .|. fromIntegral b) 0 bytes+  | otherwise = Left "is not a 64-bit integer"++decodeTxHash :: ByteString -> Either String TxHash+decodeTxHash = decodeFully "is not a transaction id: it"++-- | A count, as a compact size integer in its shortest form+decodeCount :: ByteString -> Either String Int+decodeCount bytes = do+  n <- first (const "is not a count") (parseAll (compactSizeOf "a count") "a count" bytes)+  when (n > fromIntegral (maxBound :: Int)) $ Left "is a count beyond any PSBT"+  return (fromIntegral n)++-- | A witness stack: the number of items, then each item after its length+decodeWitness :: ByteString -> Either String WitnessStack+decodeWitness = parseAll stack "the final witness"+  where+    stack = do+      count <- lengthOf "the final witness"+      replicateM count (sizedBytes "the final witness")++encodeWitness :: WitnessStack -> ByteString+encodeWitness stack = compactSize (fromIntegral (length stack)) <> B.concat (sized <$> stack)++{- Derivations -}++-- | How a key derives: the fingerprint of the key the path starts from,+-- the master key's as the spec wants it, and the path from it. A key+-- whose origin is unknown is named by its own fingerprint and an empty+-- path, or by the fingerprint of an ancestor and the path from it.+data Derivation = Derivation+  { fingerprint :: !Fingerprint,+    path :: !DerivPath+  }+  deriving (Eq, Show, Read, Generic)+  deriving anyclass (NFData)++encodeDerivation :: Derivation -> ByteString+encodeDerivation derivation =+  S.encode derivation.fingerprint <> B.concat (word32le <$> pathToList derivation.path)++-- | A derivation: four bytes of fingerprint, then four per index of the+-- path, little-endian+decodeDerivation :: ByteString -> Either String Derivation+decodeDerivation bytes = do+  unless (B.length bytes >= 4 && (B.length bytes - 4) `mod` 4 == 0) $+    Left "is not a fingerprint and a path"+  let (fingerprintBytes, pathBytes) = B.splitAt 4 bytes+  fingerprint <- decodeFully "is not a fingerprint: it" fingerprintBytes+  indices <- mapM decodeWord32le (chunksOf 4 pathBytes)+  return $ Derivation fingerprint (listToPath indices)++chunksOf :: Int -> ByteString -> [ByteString]+chunksOf n bytes+  | B.null bytes = []+  | otherwise = let (chunk, rest) = B.splitAt n bytes in chunk : chunksOf n rest++-- | The records of the type whose keys are public keys, in the order of+-- the map, with their values decoded+publicKeyRecords :: Word8 -> Ctx -> (ByteString -> Either String a) -> PsbtMap -> Either String [(PublicKey, a)]+publicKeyRecords t ctx decodeValue m =+  forM [(key, value) | (key, value) <- Map.toAscList m, key.keyType == t] $ \(key, value) -> do+    public <- first (const ("The key " <> showKey key <> " is not a public key")) (unmarshal ctx key.keyData)+    decoded <- first (("The value of the key " <> showKey key <> " ") <>) (decodeValue value)+    return (public, decoded)++derivationsOf :: Word8 -> Ctx -> PsbtMap -> Either String [(PublicKey, Derivation)]+derivationsOf t ctx = publicKeyRecords t ctx decodeDerivation++insertDerivation :: Word8 -> Ctx -> PublicKey -> Derivation -> PsbtMap -> PsbtMap+insertDerivation t ctx public derivation = Map.insert (Key t (marshal ctx public)) (encodeDerivation derivation)++-- | The derivations of the keys of an input: PSBT_IN_BIP32_DERIVATION+inputDerivations :: Ctx -> PsbtMap -> Either String [(PublicKey, Derivation)]+inputDerivations = derivationsOf inDerivationType++insertInputDerivation :: Ctx -> PublicKey -> Derivation -> PsbtMap -> PsbtMap+insertInputDerivation = insertDerivation inDerivationType++-- | The derivations of the keys of an output: PSBT_OUT_BIP32_DERIVATION+outputDerivations :: Ctx -> PsbtMap -> Either String [(PublicKey, Derivation)]+outputDerivations = derivationsOf outDerivationType++insertOutputDerivation :: Ctx -> PublicKey -> Derivation -> PsbtMap -> PsbtMap+insertOutputDerivation = insertDerivation outDerivationType++-- | The extended public keys of the global map, with their derivations:+-- PSBT_GLOBAL_XPUB. Those of another network, by their version bytes, are+-- left aside.+xpubRecords :: Network -> Ctx -> PsbtMap -> Either String [(XPubKey, Derivation)]+xpubRecords net ctx m =+  fmap catMaybes . forM [(key, value) | (key, value) <- Map.toAscList m, key.keyType == xpubType] $ \(key, value) -> do+    derivation <- first (("The value of the key " <> showKey key <> " ") <>) (decodeDerivation value)+    return $ (\xpub -> (xpub, derivation)) <$> eitherToMaybe (unmarshal (net, ctx) key.keyData)++insertXpubRecord :: Network -> Ctx -> XPubKey -> Derivation -> PsbtMap -> PsbtMap+insertXpubRecord net ctx xpub derivation = Map.insert (Key xpubType (marshal (net, ctx) xpub)) (encodeDerivation derivation)++{- The fields of an input and of an output -}++-- | The field of a type whose key carries no data, if the map has it+plainField :: Word8 -> PsbtMap -> Maybe ByteString+plainField t = Map.lookup (Key t B.empty)++insertPlainField :: Word8 -> ByteString -> PsbtMap -> PsbtMap+insertPlainField t = Map.insert (Key t B.empty)++deletePlainField :: Word8 -> PsbtMap -> PsbtMap+deletePlainField t = Map.delete (Key t B.empty)++-- | The whole transaction the input spends from: PSBT_IN_NON_WITNESS_UTXO+nonWitnessUtxo :: PsbtMap -> Either String (Maybe Tx)+nonWitnessUtxo = traverse (first ("The previous transaction " <>) . decodeTx) . plainField nonWitnessUtxoType++insertNonWitnessUtxo :: Tx -> PsbtMap -> PsbtMap+insertNonWitnessUtxo = insertPlainField nonWitnessUtxoType . S.encode++-- | The output the input spends, alone: PSBT_IN_WITNESS_UTXO+witnessUtxo :: PsbtMap -> Either String (Maybe TxOut)+witnessUtxo = traverse (first ("The witness output " <>) . decodeTxOut) . plainField witnessUtxoType++insertWitnessUtxo :: TxOut -> PsbtMap -> PsbtMap+insertWitnessUtxo = insertPlainField witnessUtxoType . S.encode++-- | The signatures of the input that are not final, by the public key each+-- is of, in the order of the map: PSBT_IN_PARTIAL_SIG. A signature is as+-- it goes in a script, with its sighash byte last.+partialSignatures :: Ctx -> PsbtMap -> Either String [(PublicKey, ByteString)]+partialSignatures ctx = publicKeyRecords partialSigType ctx Right++-- | Whether the input carries a signature that is not final+hasPartialSignatures :: PsbtMap -> Bool+hasPartialSignatures = any ((== partialSigType) . (.keyType)) . Map.keys++insertPartialSignature :: Ctx -> PublicKey -> ByteString -> PsbtMap -> PsbtMap+insertPartialSignature ctx public = Map.insert (Key partialSigType (marshal ctx public))++-- | The map without its signatures that are not final, as a finalizer+-- leaves it+deletePartialSignatures :: PsbtMap -> PsbtMap+deletePartialSignatures = Map.filterWithKey (\key _ -> key.keyType /= partialSigType)++-- | The sighash the input is to be signed with: PSBT_IN_SIGHASH_TYPE+sigHashType :: PsbtMap -> Either String (Maybe SigHash)+sigHashType = traverse (fmap fromIntegral . first ("The sighash type " <>) . decodeWord32le) . plainField sigHashTypeType++insertSigHashType :: SigHash -> PsbtMap -> PsbtMap+insertSigHashType = insertPlainField sigHashTypeType . word32le . fromIntegral++-- | The map without the sighash the input was to be signed with, as a+-- finalizer leaves it+deleteSigHashType :: PsbtMap -> PsbtMap+deleteSigHashType = deletePlainField sigHashTypeType++-- | The script of a type whose key carries no data, if the map has it+scriptField :: Word8 -> String -> PsbtMap -> Either String (Maybe Script)+scriptField t what = traverse (first ((what <> " ") <>) . decodeScript) . plainField t++insertScriptField :: Word8 -> Script -> PsbtMap -> PsbtMap+insertScriptField t = insertPlainField t . S.encode++-- | The redeem script of the P2SH coin an input spends:+-- PSBT_IN_REDEEM_SCRIPT+inputRedeemScript :: PsbtMap -> Either String (Maybe Script)+inputRedeemScript = scriptField inRedeemScriptType "The redeem script"++insertInputRedeemScript :: Script -> PsbtMap -> PsbtMap+insertInputRedeemScript = insertScriptField inRedeemScriptType++-- | The witness script of the P2WSH coin an input spends:+-- PSBT_IN_WITNESS_SCRIPT+inputWitnessScript :: PsbtMap -> Either String (Maybe Script)+inputWitnessScript = scriptField inWitnessScriptType "The witness script"++insertInputWitnessScript :: Script -> PsbtMap -> PsbtMap+insertInputWitnessScript = insertScriptField inWitnessScriptType++-- | The input script of a signed input: PSBT_IN_FINAL_SCRIPTSIG+finalScriptSig :: PsbtMap -> Maybe ByteString+finalScriptSig = plainField finalScriptSigType++insertFinalScriptSig :: ByteString -> PsbtMap -> PsbtMap+insertFinalScriptSig = insertPlainField finalScriptSigType++-- | The witness of a signed input: PSBT_IN_FINAL_SCRIPTWITNESS+finalScriptWitness :: PsbtMap -> Either String (Maybe WitnessStack)+finalScriptWitness = traverse decodeWitness . plainField finalScriptWitnessType++insertFinalScriptWitness :: WitnessStack -> PsbtMap -> PsbtMap+insertFinalScriptWitness = insertPlainField finalScriptWitnessType . encodeWitness++-- | The redeem script of a P2SH output: PSBT_OUT_REDEEM_SCRIPT+outputRedeemScript :: PsbtMap -> Either String (Maybe Script)+outputRedeemScript = scriptField outRedeemScriptType "The redeem script"++insertOutputRedeemScript :: Script -> PsbtMap -> PsbtMap+insertOutputRedeemScript = insertScriptField outRedeemScriptType++-- | The witness script of a P2WSH output: PSBT_OUT_WITNESS_SCRIPT+outputWitnessScript :: PsbtMap -> Either String (Maybe Script)+outputWitnessScript = scriptField outWitnessScriptType "The witness script"++insertOutputWitnessScript :: Script -> PsbtMap -> PsbtMap+insertOutputWitnessScript = insertScriptField outWitnessScriptType++{- The Taproot fields of BIP 371 -}++-- | The key path signature of a Taproot input: PSBT_IN_TAP_KEY_SIG+tapKeySig :: PsbtMap -> Either String (Maybe TaprootSignature)+tapKeySig = optionalField "The key path signature" tapKeySigType decodeTaprootSig++insertTapKeySig :: TaprootSignature -> PsbtMap -> PsbtMap+insertTapKeySig = insertPlainField tapKeySigType . encodeTaprootSig++-- | The script path signatures of a Taproot input, each by the x-only key+-- and the leaf hash it is for: PSBT_IN_TAP_SCRIPT_SIG+tapScriptSigs :: Ctx -> PsbtMap -> Either String [((PubKey, Digest SHA256), TaprootSignature)]+tapScriptSigs ctx m =+  forM [(key, value) | (key, value) <- Map.toAscList m, key.keyType == tapScriptSigType] $ \(key, value) -> do+    let (keyBytes, leafBytes) = B.splitAt 32 key.keyData+    leafKey <- maybe (Left ("The key " <> showKey key <> " is not an x-only public key and a leaf hash")) Right $ importXOnlyPubKey ctx keyBytes+    leaf <- maybe (Left ("The key " <> showKey key <> " is not an x-only public key and a leaf hash")) Right $ digestOf leafBytes+    signature <- first (("The value of the key " <> showKey key <> " ") <>) (decodeTaprootSig value)+    return ((leafKey, leaf), signature)++insertTapScriptSig :: Ctx -> PubKey -> Digest SHA256 -> TaprootSignature -> PsbtMap -> PsbtMap+insertTapScriptSig ctx leafKey leaf signature =+  Map.insert (Key tapScriptSigType (exportXOnlyPubKey ctx leafKey <> BA.convert leaf)) (encodeTaprootSig signature)++-- | A leaf script a Taproot input may be spent with, and the control+-- block that commits it to the output key: PSBT_IN_TAP_LEAF_SCRIPT+data TapLeafScript = TapLeafScript+  { controlBlock :: !ByteString,+    script :: !Script,+    leafVersion :: !TapLeafVersion+  }+  deriving (Eq, Show, Read, Generic)+  deriving anyclass (NFData)++-- | The leaf scripts of a Taproot input, with their control blocks:+-- PSBT_IN_TAP_LEAF_SCRIPT+tapLeafScripts :: Ctx -> PsbtMap -> Either String [TapLeafScript]+tapLeafScripts ctx m =+  forM [(key, value) | (key, value) <- Map.toAscList m, key.keyType == tapLeafScriptType] $ \(key, value) -> do+    _ <- first (const ("The key " <> showKey key <> " is not a control block")) (decodeControlBlock ctx key.keyData)+    when (B.null value) $ Left ("The value of the key " <> showKey key <> " has no leaf version")+    script <- first (("The value of the key " <> showKey key <> " ") <>) (decodeScript (B.init value))+    return (TapLeafScript key.keyData script (B.last value))++insertTapLeafScript :: TapLeafScript -> PsbtMap -> PsbtMap+insertTapLeafScript leaf =+  Map.insert (Key tapLeafScriptType leaf.controlBlock) (S.encode leaf.script <> B.singleton leaf.leafVersion)++-- | How an x-only key of a Taproot input or output derives, and the+-- leaves it appears in: the value of PSBT_IN_TAP_BIP32_DERIVATION and+-- PSBT_OUT_TAP_BIP32_DERIVATION. The internal key has no leaf.+data TapDerivation = TapDerivation+  { leafHashes :: ![Digest SHA256],+    derivation :: !Derivation+  }+  deriving (Eq, Show, Generic)++encodeTapDerivation :: TapDerivation -> ByteString+encodeTapDerivation d =+  compactSize (fromIntegral (length d.leafHashes)) <> B.concat (BA.convert <$> d.leafHashes) <> encodeDerivation d.derivation++-- | A Taproot derivation: the number of leaf hashes, the hashes, then the+-- fingerprint and the path+decodeTapDerivation :: ByteString -> Either String TapDerivation+decodeTapDerivation = parseAll record "a Taproot derivation"+  where+    record = do+      count <- lengthOf "a Taproot derivation"+      hashes <- replicateM count (bytesOf 32 "a Taproot derivation")+      leaves <- maybe (refuse "is not a Taproot derivation") return (mapM digestOf hashes)+      rest <- restOf+      derivation <- either refuse return (decodeDerivation rest)+      return (TapDerivation leaves derivation)++tapDerivationsOf :: Word8 -> Ctx -> PsbtMap -> Either String [(PubKey, TapDerivation)]+tapDerivationsOf t ctx m =+  forM [(key, value) | (key, value) <- Map.toAscList m, key.keyType == t] $ \(key, value) -> do+    public <- maybe (Left ("The key " <> showKey key <> " is not an x-only public key")) Right (importXOnlyPubKey ctx key.keyData)+    decoded <- first (("The value of the key " <> showKey key <> " ") <>) (decodeTapDerivation value)+    return (public, decoded)++insertTapDerivation :: Word8 -> Ctx -> PubKey -> TapDerivation -> PsbtMap -> PsbtMap+insertTapDerivation t ctx public d = Map.insert (Key t (exportXOnlyPubKey ctx public)) (encodeTapDerivation d)++-- | The derivations of the x-only keys of a Taproot input, each with the+-- leaves it appears in: PSBT_IN_TAP_BIP32_DERIVATION. The key is the+-- point of even y with that x coordinate.+inputTapDerivations :: Ctx -> PsbtMap -> Either String [(PubKey, TapDerivation)]+inputTapDerivations = tapDerivationsOf inTapDerivationType++insertInputTapDerivation :: Ctx -> PubKey -> TapDerivation -> PsbtMap -> PsbtMap+insertInputTapDerivation = insertTapDerivation inTapDerivationType++-- | The derivations of the x-only keys of a Taproot output:+-- PSBT_OUT_TAP_BIP32_DERIVATION+outputTapDerivations :: Ctx -> PsbtMap -> Either String [(PubKey, TapDerivation)]+outputTapDerivations = tapDerivationsOf outTapDerivationType++insertOutputTapDerivation :: Ctx -> PubKey -> TapDerivation -> PsbtMap -> PsbtMap+insertOutputTapDerivation = insertTapDerivation outTapDerivationType++xOnlyKeyField :: Word8 -> String -> Ctx -> PsbtMap -> Either String (Maybe PubKey)+xOnlyKeyField t what ctx = optionalField what t (maybe (Left "is not an x-only public key") Right . importXOnlyPubKey ctx)++insertXOnlyKeyField :: Word8 -> Ctx -> PubKey -> PsbtMap -> PsbtMap+insertXOnlyKeyField t ctx = insertPlainField t . exportXOnlyPubKey ctx++-- | The internal key of the Taproot coin an input spends:+-- PSBT_IN_TAP_INTERNAL_KEY. The key is the point of even y with that x+-- coordinate.+inputTapInternalKey :: Ctx -> PsbtMap -> Either String (Maybe PubKey)+inputTapInternalKey = xOnlyKeyField inTapInternalKeyType "The internal key"++insertInputTapInternalKey :: Ctx -> PubKey -> PsbtMap -> PsbtMap+insertInputTapInternalKey = insertXOnlyKeyField inTapInternalKeyType++-- | The root of the script tree of the Taproot coin an input spends:+-- PSBT_IN_TAP_MERKLE_ROOT+inputTapMerkleRoot :: PsbtMap -> Either String (Maybe (Digest SHA256))+inputTapMerkleRoot = optionalField "The Merkle root" tapMerkleRootType (maybe (Left "is not a 32-byte hash") Right . digestOf)++insertInputTapMerkleRoot :: Digest SHA256 -> PsbtMap -> PsbtMap+insertInputTapMerkleRoot = insertPlainField tapMerkleRootType . BA.convert++-- | The internal key of a Taproot output: PSBT_OUT_TAP_INTERNAL_KEY+outputTapInternalKey :: Ctx -> PsbtMap -> Either String (Maybe PubKey)+outputTapInternalKey = xOnlyKeyField outTapInternalKeyType "The internal key"++insertOutputTapInternalKey :: Ctx -> PubKey -> PsbtMap -> PsbtMap+insertOutputTapInternalKey = insertXOnlyKeyField outTapInternalKeyType++-- | The script tree of a Taproot output, as its leaves depth first:+-- PSBT_OUT_TAP_TREE. 'tapTreeFromLeaves' builds the tree.+outputTapTree :: PsbtMap -> Either String (Maybe [TapLeaf])+outputTapTree = optionalField "The script tree" outTapTreeType decodeTapTree++insertOutputTapTree :: [TapLeaf] -> PsbtMap -> PsbtMap+insertOutputTapTree leaves = insertPlainField outTapTreeType (B.concat (encodeLeaf <$> leaves))+  where+    encodeLeaf :: TapLeaf -> ByteString+    encodeLeaf leaf = B.pack [leaf.depth, leaf.version] <> sized (S.encode leaf.script)++-- | A script tree: for each leaf, its depth, its leaf version and its+-- script after its length+decodeTapTree :: ByteString -> Either String [TapLeaf]+decodeTapTree = parseAll leaves "a script tree"+  where+    leaves = do+      rest <- restOf+      if B.null rest+        then return []+        else do+          BytesParser (\_ -> Right ((), rest)) -- put the bytes back+          depth <- B.head <$> bytesOf 1 "a script tree"+          version <- B.head <$> bytesOf 1 "a script tree"+          scriptBytes <- sizedBytes "a script tree"+          script <- either refuse return (decodeScript scriptBytes)+          (TapLeaf depth version script :) <$> leaves++-- | A 32-byte hash as a digest+digestOf :: ByteString -> Maybe (Digest SHA256)+digestOf = digestFromByteString++{- The fields of version 2 -}++-- | The flags of PSBT_GLOBAL_TX_MODIFIABLE: whether inputs may be added,+-- whether outputs may be added, and whether an input is signed with+-- SIGHASH_SINGLE, which pairs it with the output at its index. The other+-- bits of the byte are not defined; they are kept as they are.+data Modifiable = Modifiable+  { inputsModifiable :: !Bool,+    outputsModifiable :: !Bool,+    hasSigHashSingle :: !Bool,+    undefinedBits :: !Word8+  }+  deriving (Eq, Show, Read, Generic)+  deriving anyclass (NFData)++-- | No flag set+noFlags :: Modifiable+noFlags = Modifiable False False False 0++-- | The flags of the global map, if it has the field: PSBT_GLOBAL_TX_MODIFIABLE+txModifiable :: PsbtMap -> Either String (Maybe Modifiable)+txModifiable = optionalField "The modifiable flags" txModifiableType decodeFlags+  where+    decodeFlags value = case B.unpack value of+      [byte] -> Right $ Modifiable (testBit byte 0) (testBit byte 1) (testBit byte 2) (byte .&. 0xf8)+      _ -> Left "are not one byte"++insertTxModifiable :: Modifiable -> PsbtMap -> PsbtMap+insertTxModifiable flags = insertPlainField txModifiableType (B.singleton byte)+  where+    byte = bit 0 flags.inputsModifiable .|. bit 1 flags.outputsModifiable .|. bit 2 flags.hasSigHashSingle .|. (flags.undefinedBits .&. 0xf8)+    bit :: Int -> Bool -> Word8+    bit n set = if set then 1 `shiftL` n else 0++-- | The global map without the flags, which a constructor may remove once+-- nothing is to be added+deleteTxModifiable :: PsbtMap -> PsbtMap+deleteTxModifiable = deletePlainField txModifiableType++-- | The lock time of the transaction when no input requires one:+-- PSBT_GLOBAL_FALLBACK_LOCKTIME+fallbackLockTime :: PsbtMap -> Either String (Maybe Word32)+fallbackLockTime = optionalField "The fallback lock time" fallbackLockTimeType decodeWord32le++-- | The least time, as a Unix timestamp, the input requires as the lock+-- time of the transaction: PSBT_IN_REQUIRED_TIME_LOCKTIME+requiredTimeLockTime :: PsbtMap -> Either String (Maybe Word32)+requiredTimeLockTime = optionalField "The required time lock time" requiredTimeType decodeWord32le++insertRequiredTimeLockTime :: Word32 -> PsbtMap -> PsbtMap+insertRequiredTimeLockTime = insertPlainField requiredTimeType . word32le++-- | The least block height the input requires as the lock time of the+-- transaction: PSBT_IN_REQUIRED_HEIGHT_LOCKTIME+requiredHeightLockTime :: PsbtMap -> Either String (Maybe Word32)+requiredHeightLockTime = optionalField "The required height lock time" requiredHeightType decodeWord32le++insertRequiredHeightLockTime :: Word32 -> PsbtMap -> PsbtMap+insertRequiredHeightLockTime = insertPlainField requiredHeightType . word32le++{- Proprietary fields -}++-- | A proprietary field, of type 0xFC: the identifier of the software it+-- belongs to, its subtype under that identifier, and the data after the+-- subtype, none for most+data Proprietary = Proprietary+  { identifier :: !ByteString,+    subtype :: !Word64,+    extra :: !ByteString+  }+  deriving (Eq, Show, Read, Generic)+  deriving anyclass (NFData)++-- | The key of a proprietary field: the type 0xFC, the identifier after+-- its length, the subtype as a compact size integer, and the data+proprietaryKey :: Proprietary -> Key+proprietaryKey p =+  Key proprietaryType (sized p.identifier <> compactSize p.subtype <> p.extra)++-- | The proprietary field a key is, if it is one: nothing for a key of+-- another type, a refusal for a proprietary key without its identifier+-- and subtype+proprietaryOf :: Key -> Either String (Maybe Proprietary)+proprietaryOf key+  | key.keyType /= proprietaryType = Right Nothing+  | otherwise =+      first (const ("the proprietary key " <> showKey key <> " has no identifier and subtype")) $+        Just <$> parseAll (Proprietary <$> sizedBytes what <*> compactSizeOf what <*> restOf) what key.keyData+  where+    what = "a proprietary key"++-- | The value of the proprietary field of the identifier and subtype,+-- with no data after the subtype, if the map has it+lookupProprietary :: ByteString -> Word64 -> PsbtMap -> Maybe ByteString+lookupProprietary identifier subtype = Map.lookup (proprietaryKey (Proprietary identifier subtype B.empty))++insertProprietary :: ByteString -> Word64 -> ByteString -> PsbtMap -> PsbtMap+insertProprietary identifier subtype = Map.insert (proprietaryKey (Proprietary identifier subtype B.empty))++{- Signing -}++-- | Where a signer gets its private keys: by a derivation record naming a+-- public key, or by the hash of a public key when the input has no record+-- for it. A key that is not held is 'Nothing'. The signer also says which+-- sighash types it agrees to sign with.+data Signer = Signer+  { -- | the private key of the public key a derivation record names+    keyOfRecord :: PublicKey -> Derivation -> Maybe SecKey,+    -- | the private key whose public key, compressed or not, has the hash+    keyOfHash :: Hash160 -> Maybe PrivateKey,+    -- | the sighash types the signer signs with. An input that asks for+    -- another type is refused, as BIP 174 asks of a signer. The fork id+    -- flag does not count, since the network decides it.+    acceptedSigHashes :: [SigHash]+  }++-- | The sighash types a signer accepts unless told otherwise:+-- 'sigHashDefault', which a Taproot input uses when it asks for none, and+-- 'sigHashAll'. The other types leave part of the transaction out of the+-- signature, so a PSBT written by someone else may not ask for them on its+-- own; a signer that agrees to them lists them, for example+-- @(keySigner ctx key) {acceptedSigHashes = sigHashNone : defaultSigHashes}@.+defaultSigHashes :: [SigHash]+defaultSigHashes = [sigHashDefault, sigHashAll]++-- | Whether the signer accepts the sighash type: it is one of its types,+-- the fork id flag aside+acceptsSigHash :: Signer -> SigHash -> Bool+acceptsSigHash signer sighash =+  withoutForkId sighash `elem` map withoutForkId signer.acceptedSigHashes+  where+    withoutForkId = (.&. complement forkIdBit)+    forkIdBit = fromIntegral (fromEnum SIGHASH_FORKID)++-- | A signer that holds one private key, for the records of its public+-- key and for the hash of its public key in either form+keySigner :: Ctx -> SecKey -> Signer+keySigner ctx secret =+  Signer+    { keyOfRecord = \public _ -> do+        guard (public.point == derivePubKey ctx secret)+        return secret,+      keyOfHash = \h -> find ((== h) . addressHash . marshal ctx . derivePublicKey ctx) [PrivateKey secret True, PrivateKey secret False],+      acceptedSigHashes = defaultSigHashes+    }++-- | A signer that derives from an extended private key. A record names a+-- key it derives by the fingerprint of the extended key and the path from+-- it, or, when the origin of the extended key is given, by the+-- fingerprint of that origin and the path from it, which starts with the+-- path of the extended key. It has no key for a hash, since it cannot+-- know which path to derive.+xPrvSigner :: Ctx -> XPrvKey -> Maybe Derivation -> Signer+xPrvSigner ctx xprv originM =+  Signer+    { keyOfRecord = \public derivation -> do+        path <- relative derivation+        let secret = (derivePath ctx path xprv).key+        guard (public.point == derivePubKey ctx secret)+        return secret,+      keyOfHash = const Nothing,+      acceptedSigHashes = defaultSigHashes+    }+  where+    own = xPubFP ctx (deriveXPubKey ctx xprv)+    relative :: Derivation -> Maybe DerivPath+    relative derivation+      | derivation.fingerprint == own = Just derivation.path+      | Just origin <- originM, derivation.fingerprint == origin.fingerprint =+          listToPath <$> stripPrefix (pathToList origin.path) (pathToList derivation.path)+      | otherwise = Nothing++-- | Sign every input the signer has keys for, as the signer of the spec+-- does. An input is signed against the coin it spends, from its previous+-- transaction, whose id must be the one the input spends, or from its+-- witness output alone, which only a witness signature may use, as BIP+-- 174 requires. The keys are those the derivation records of the input+-- name, and those the coin, its redeem script or its witness script name,+-- by hash. The sighash is the one the input asks for, SIGHASH_ALL when it+-- asks for none; a type no node accepts, or one the signer does not accept+-- ('acceptedSigHashes'), is refused. A legacy coin asked to sign+-- SIGHASH_SINGLE at an input with no output is refused too, as+-- 'makeSignature' explains. A P2SH coin needs its redeem script in the+-- input, and a P2WSH coin its witness script; a script that does not hash+-- to the coin is refused.+--+-- A Taproot coin is signed by its key path when the input has its internal+-- key, which with the Merkle root must give the output key, and by each of+-- its leaf scripts that is a single key check (@<key> OP_CHECKSIG@), with+-- 'sigHashDefault' when the input asks for no sighash. The BIP 341 digest+-- commits to the output every input spends, so every input must have its+-- coin then.+--+-- An input without its coin, with a coin whose script is not standard, or+-- with a final script already, is left as it is.+signPsbt :: Network -> Ctx -> Signer -> Psbt -> Either String Psbt+signPsbt net ctx signer psbt = do+  coins <- spentOutputs psbt+  let tx = psbt.unsignedTx+      allCoins = mapM (fmap fst) coins+  signed <- forM (zip3 [0 ..] (zip tx.inputs psbt.inputs) coins) $ \(i, (input, m), coinM) ->+    first (("Input " <> show i <> ": ") <>) $ case coinM of+      Just (coin, witnessOnly)+        | not (isFinal m),+          Right coinScript <- unmarshal ctx coin.script -> do+            sighashM <- sigHashType m+            if isTaprootCoin coinScript+              then signTaproot i m coin coinScript sighashM allCoins+              else signEcdsa i input m coin coinScript witnessOnly sighashM+      _ -> return (m, [])+  return $ foldr afterSignature (Psbt psbt.version tx psbt.globals (fst <$> signed) psbt.outputs) (concatMap snd signed)+  where+    tx = psbt.unsignedTx++    -- A signature per key the signer has, for a coin signed with ECDSA+    signEcdsa i input m coin coinScript witnessOnly sighashM = do+      let sighash = fromMaybe sigHashAll sighashM+      checkEcdsaSigHash net sighash+      unless (acceptsSigHash signer sighash) $+        Left (notAcceptedSigHash sighash)+      sigInput <- signingInput ctx input coin coinScript sighash m+      when (witnessOnly && not (isSegwit sigInput.script)) $+        Left "the coin is given as a witness output only, which may not be signed with a non-witness signature"+      records <- inputDerivations ctx m+      let recordKeys = [PrivateKey secret public.compress | (public, d) <- records, Just secret <- [signer.keyOfRecord public d]]+          hashKeys = mapMaybe signer.keyOfHash (scriptKeyHashes ctx coinScript sigInput.redeem)+          keys = nubBy (\a b -> derivePublicKey ctx a == derivePublicKey ctx b) (recordKeys <> hashKeys)+      signatures <- forM keys $ \key -> do+        signature <- makeSignature net ctx tx i sigInput key+        return (derivePublicKey ctx key, encodeTxSig net ctx signature)+      return (foldr (\(public, sig) -> insertPartialSignature ctx public sig) m signatures, [sighash | not (null keys)])++    -- A key path signature, and one per leaf that is a single key check,+    -- for a Taproot coin+    signTaproot i m coin coinScript sighashM allCoins = do+      let sighash = fromMaybe sigHashDefault sighashM+      unless (isTaprootSigHash sighash) $+        Left ("the sighash type " <> show (fromIntegral sighash :: Word32) <> " is not one a Taproot signature may use")+      unless (acceptsSigHash signer sighash) $+        Left (notAcceptedSigHash sighash)+      spent <- maybe (Left "a Taproot signature needs the output every input spends") Right allCoins+      program <- taprootProgram coinScript+      internalM <- inputTapInternalKey ctx m+      rootM <- inputTapMerkleRoot m+      records <- inputTapDerivations ctx m+      leaves <- tapLeafScripts ctx m+      let heldXOnly leaf public =+            listToMaybe . catMaybes $+              [signer.keyOfRecord (PublicKey candidate True) d.derivation | (recordKey, d) <- records, sameX recordKey public, leaf `elem` map Just d.leafHashes || (leaf == Nothing && null d.leafHashes), candidate <- bothParities public]+                <> [(.key) <$> signer.keyOfHash (addressHash (marshal ctx (PublicKey candidate True))) | candidate <- bothParities public]+      keySig <- case internalM of+        Nothing -> return []+        Just internal -> do+          let expected = taprootOutputKey ctx (TaprootOutput internal (MASTCommitment <$> rootM))+          unless (exportXOnlyPubKey ctx expected == program) $+            Left "the internal key and the Merkle root do not give the output key of the coin"+          case heldXOnly Nothing internal of+            Nothing -> return []+            Just secret -> do+              tweaked <- maybe (Left "the key cannot be tweaked") Right (taprootOutputSecKey ctx secret (MASTCommitment <$> rootM))+              h <- taprootSigHash tx spent i sighash Nothing Nothing+              return [insertTapKeySig (TaprootSignature (signSchnorr ctx tweaked (hashToMsg h)) sighash)]+      scriptSigs <- fmap concat . forM leaves $ \leaf -> case singleKeyLeaf ctx leaf of+        Just leafKey+          | Just secret <- heldXOnly (Just (leafHash leaf.leafVersion leaf.script)) leafKey -> do+              let hashOfLeaf = leafHash leaf.leafVersion leaf.script+              h <- taprootSigHash tx spent i sighash Nothing (Just (hashOfLeaf, 0xffffffff))+              return [insertTapScriptSig ctx leafKey hashOfLeaf (TaprootSignature (signSchnorr ctx secret (hashToMsg h)) sighash)]+        _ -> return []+      let inserts = keySig <> scriptSigs+      return (foldr ($) m inserts, [sighash | not (null inserts)])++    sameX a b = exportXOnlyPubKey ctx a == exportXOnlyPubKey ctx b+    -- The two points with the x coordinate of the key+    bothParities public =+      mapMaybe (\prefix -> (.point) <$> eitherToMaybe (unmarshal ctx (B.cons prefix (exportXOnlyPubKey ctx public)) :: Either String PublicKey)) [0x02, 0x03]++-- | The hashes of the keys a coin is signed with, from the script that+-- names them: the coin, or its redeem or witness script+scriptKeyHashes :: Ctx -> ScriptOutput -> Maybe RedeemScript -> [Hash160]+scriptKeyHashes ctx coinScript innerM = case fromMaybe coinScript innerM of+  PayPK public -> [addressHash (marshal ctx public)]+  PayPKHash h -> [h]+  PayWitnessPKHash h -> [h]+  PayMulSig publics _ -> map (addressHash . marshal ctx) publics+  _ -> []++-- | The leaf key of a leaf script that is a single key check, @<key>+-- OP_CHECKSIG@ of leaf version 0xc0+singleKeyLeaf :: Ctx -> TapLeafScript -> Maybe PubKey+singleKeyLeaf ctx leaf = case leaf.script.ops of+  [OP_PUSHDATA keyBytes OPCODE, OP_CHECKSIG]+    | leaf.leafVersion == tapscriptLeafVersion -> importXOnlyPubKey ctx keyBytes+  _ -> Nothing++-- | The error for a sighash type the signer does not accept+notAcceptedSigHash :: SigHash -> String+notAcceptedSigHash sighash =+  "the sighash type " <> show (fromIntegral sighash :: Word32) <> " is not one the signer accepts"++-- | A sighash type an ECDSA signature may carry on the network: one of+-- ALL, NONE and SINGLE, with ANYONECANPAY or not, and the fork id flag+-- exactly on the networks with a fork id+checkEcdsaSigHash :: Network -> SigHash -> Either String ()+checkEcdsaSigHash net sighash = do+  when (isSigHashUnknown sighash) $+    Left ("the sighash type " <> show (fromIntegral sighash :: Word32) <> " is not one a node accepts")+  when (isJust net.sigHashForkId && not (hasForkIdFlag sighash)) $+    Left "the sighash type lacks the fork id flag the network requires"+  when (isNothing' net.sigHashForkId && hasForkIdFlag sighash) $+    Left "the sighash type has a fork id flag the network does not use"+  where+    isNothing' = maybe True (const False)++-- | Whether a coin is a Taproot output, and its program+isTaprootCoin :: ScriptOutput -> Bool+isTaprootCoin = either (const False) (const True) . taprootProgram++taprootProgram :: ScriptOutput -> Either String ByteString+taprootProgram = \case+  PayWitness 1 program | B.length program == 32 -> Right program+  _ -> Left "not a Taproot output"++-- | The PSBT after a signature with the sighash was added, as the signer+-- of BIP 370 leaves the flags of version 2: inputs may no longer be added+-- unless the sighash has ANYONECANPAY, outputs no longer unless it is+-- NONE, and the PSBT has a SIGHASH_SINGLE signature when it is SINGLE. A+-- PSBT of version 0 has no flags.+afterSignature :: SigHash -> Psbt -> Psbt+afterSignature sighash psbt = case psbt.version of+  Version0 -> psbt+  Version2+    | isJust beforeM || after /= noFlags -> Psbt Version2 psbt.unsignedTx (insertTxModifiable after psbt.globals) psbt.inputs psbt.outputs+    | otherwise -> psbt+  where+    beforeM = either (const Nothing) id (txModifiable psbt.globals)+    before = fromMaybe noFlags beforeM+    after =+      before+        { inputsModifiable = before.inputsModifiable && anyoneCanPay sighash,+          outputsModifiable = before.outputsModifiable && isSigHashNone sighash,+          hasSigHashSingle = before.hasSigHashSingle || isSigHashSingle sighash+        }++-- | Whether an input has its final script or witness+isFinal :: PsbtMap -> Bool+isFinal m = any (`Map.member` m) [Key finalScriptSigType B.empty, Key finalScriptWitnessType B.empty]++-- | The output every input spends, from the fields of each input, and+-- whether it is known from the witness output alone+spentOutputs :: Psbt -> Either String [Maybe (TxOut, Bool)]+spentOutputs psbt =+  forM (zip3 [0 :: Int ..] psbt.unsignedTx.inputs psbt.inputs) $ \(i, input, m) ->+    first (("Input " <> show i <> ": ") <>) (spentOutput input m)++-- | The output an input spends, from the fields of the input: the output+-- of its previous transaction, which must be the transaction the input+-- spends from, or else its witness output alone. With both, the witness+-- output must be that output. The flag tells when the witness output+-- alone is known, which only a witness signature may rely on.+spentOutput :: TxIn -> PsbtMap -> Either String (Maybe (TxOut, Bool))+spentOutput input m = do+  witnessM <- witnessUtxo m+  parentM <- nonWitnessUtxo m+  case (parentM, witnessM) of+    (Just parent, _) -> do+      unless (txHash parent == input.outpoint.hash) $+        Left "The previous transaction of the input is not the one it spends"+      coin <-+        maybe (Left "The input spends an output its previous transaction does not have") return $+          listToMaybe (drop (fromIntegral input.outpoint.index) parent.outputs)+      forM_ witnessM $ \witnessCoin ->+        unless (witnessCoin == coin) $+          Left "The witness output of the input is not the output of its previous transaction"+      return (Just (coin, False))+    (Nothing, Just coin) -> return (Just (coin, True))+    (Nothing, Nothing) -> return Nothing++-- | What an input is signed against: the script of its coin, with the+-- redeem script of a P2SH coin and the witness script of a P2WSH coin+-- from the input, as 'makeSignature' wants them. A redeem script must+-- hash to the coin, and a witness script to the witness program, as BIP+-- 174 requires of a signer.+signingInput :: Ctx -> TxIn -> TxOut -> ScriptOutput -> SigHash -> PsbtMap -> Either String SigInput+signingInput ctx input coin coinScript sighash m = do+  redeemM <- traverse (decodeOutput ctx) =<< inputRedeemScript m+  witnessM <- traverse (decodeOutput ctx) =<< inputWitnessScript m+  forM_ redeemM $ \redeem -> do+    unless (isPayScriptHash coinScript) $ Left "the input has a redeem script, but its coin is not pay-to-script-hash"+    redeemScript <- encodeOutput ctx redeem+    unless (toP2SH redeemScript == coinScript) $ Left "the redeem script of the input does not hash to its coin"+  -- A SegWit coin, nested in P2SH or not, is signed against its witness+  -- program; any other against its own script+  let program = case redeemM of+        Just redeem | not (isSegwit coinScript), isSegwit redeem -> redeem+        _ -> coinScript+  forM_ witnessM $ \witness -> do+    unless (isPayWitnessScriptHash program) $ Left "the input has a witness script, but its coin is not pay-to-witness-script-hash"+    witnessScript <- encodeOutput ctx witness+    unless (toP2WSH witnessScript == program) $ Left "the witness script of the input does not hash to its witness program"+  return+    SigInput+      { script = program,+        value = coin.value,+        outpoint = input.outpoint,+        sighash = sighash,+        redeem = case program of+          PayWitnessScriptHash _ -> witnessM+          PayScriptHash _ -> redeemM+          _ -> Nothing+      }++{- Finalizing -}++-- | Finalize every input whose signatures suffice for the coin it spends,+-- as the finalizer of the spec does: the final script and witness go in+-- the input, and its partial signatures, sighash type, redeem script,+-- witness script, derivation records and Taproot fields go, as Bitcoin+-- Core clears them. Its coin and the keys the spec does not define stay.+-- Every signature used is verified first, against the digest of the coin+-- and the sighash type of the input when it has one; a signature that+-- does not verify, or whose sighash type is not the one of the input, is+-- refused, as @PSBTInputSignedAndVerified@ refuses it in Bitcoin Core.+-- The coins finalized are those of P2PK, P2PKH and multisig scripts, P2SH+-- coins with their redeem script, P2WPKH coins, P2WSH coins with their+-- witness script, nested in P2SH or not, and Taproot coins by their key+-- path signature or by a leaf script that is a single key check. Any+-- other input, or one without enough signatures, stays as it is.+finalizePsbt :: Network -> Ctx -> Psbt -> Either String Psbt+finalizePsbt net ctx psbt = do+  coins <- spentOutputs psbt+  let allCoins = mapM (fmap fst) coins+  inputs <- forM (zip3 [0 ..] (zip tx.inputs psbt.inputs) coins) $ \(i, (input, m), coinM) ->+    first (("Input " <> show i <> ": ") <>) $ case coinM of+      Just (coin :: TxOut, _)+        | not (isFinal m),+          Right coinScript <- unmarshal ctx coin.script -> do+            finals <-+              if isTaprootCoin coinScript+                then finalTaprootScripts ctx tx allCoins i m coinScript+                else finalScripts net ctx tx i input coin coinScript m+            return $ case finals of+              Just (scriptSig, stack) ->+                foldr ($) (cleared m) $+                  [insertFinalScriptSig scriptSig | not (B.null scriptSig)]+                    <> [insertFinalScriptWitness stack | not (null stack)]+              Nothing -> m+      _ -> return m+  return $ Psbt psbt.version tx psbt.globals inputs psbt.outputs+  where+    tx = psbt.unsignedTx+    cleared = Map.filterWithKey (\key _ -> key.keyType `notElem` clearedTypes)+    clearedTypes =+      [partialSigType, sigHashTypeType, inRedeemScriptType, inWitnessScriptType, inDerivationType, tapKeySigType, tapScriptSigType, tapLeafScriptType, inTapDerivationType, inTapInternalKeyType, tapMerkleRootType]++-- | The final script and witness of an input signed with ECDSA, from its+-- partial signatures and the script of its coin, when they suffice. Every+-- signature is verified first.+finalScripts :: Network -> Ctx -> Tx -> Int -> TxIn -> TxOut -> ScriptOutput -> PsbtMap -> Either String (Maybe (ByteString, WitnessStack))+finalScripts net ctx tx i input coin coinScript m = do+  sighashM <- sigHashType m+  signatures <- partialSignatures ctx m+  redeemM <- inputRedeemScript m+  witnessM <- inputWitnessScript m+  sigInput <- signingInput ctx input coin coinScript (fromMaybe sigHashAll sighashM) m+  forM_ signatures $ \(public, bytes) -> do+    (sig, sh) <- case decodeTxSig net ctx bytes of+      Right (TxSignature sig sh) -> return (sig, sh)+      _ -> Left ("the signature of the key " <> cs' (marshal ctx public) <> " is not a valid signature")+    forM_ sighashM $ \asked ->+      unless (sh == asked) $ Left ("the signature of the key " <> cs' (marshal ctx public) <> " does not use the sighash type of the input")+    digest <- makeSigHash net ctx tx i sigInput.script coin.value sh sigInput.redeem+    unless (verifyHashSig ctx digest sig public.point) $+      Left ("the signature of the key " <> cs' (marshal ctx public) <> " does not verify")+  let signatureOf public = lookup public signatures+      signatureOfHash h = listToMaybe [(public, signature) | (public, signature) <- signatures, addressHash (marshal ctx public) == h]+      enough keys n = let found = mapMaybe signatureOf keys in if length found >= n then Just (take n found) else Nothing+      -- The items a script is spent with: the pushes of an input script,+      -- or the items of a witness+      items script = case script of+        PayPK public -> (: []) <$> signatureOf public+        PayPKHash h -> withKey <$> signatureOfHash h+        PayMulSig keys n -> (B.empty :) <$> enough keys n+        PayWitnessPKHash h -> withKey <$> signatureOfHash h+        _ -> Nothing+      withKey :: (PublicKey, ByteString) -> [ByteString]+      withKey (public, signature) = [signature, marshal ctx public]+      scriptOf = S.encode . Script . map opPushData+      -- The witness of a SegWit coin: the items of its program, with the+      -- witness script last for P2WSH+      witnessOf script = case script of+        PayWitnessScriptHash _ -> do+          witnessScript <- witnessM+          guard (toP2WSH witnessScript == script)+          inner <- eitherToMaybe (decodeOutput ctx witnessScript)+          (<> [S.encode witnessScript]) <$> items inner+        _ -> items script+  return $ case coinScript of+    PayScriptHash _ -> do+      redeem <- redeemM+      guard (toP2SH redeem == coinScript)+      inner <- eitherToMaybe (decodeOutput ctx redeem)+      if isSegwit inner+        then (scriptOf [S.encode redeem],) <$> witnessOf inner+        else (\found -> (scriptOf (found <> [S.encode redeem]), [])) <$> items inner+    _+      | isSegwit coinScript -> (B.empty,) <$> witnessOf coinScript+      | otherwise -> (\found -> (scriptOf found, [])) <$> items coinScript+  where+    cs' = show . encodeHex++-- | The final witness of a Taproot input: its key path signature, or the+-- signature of the first leaf script that is a single key check with the+-- script and its control block. Every signature is verified first,+-- against the output key and the digest of its leaf.+finalTaprootScripts :: Ctx -> Tx -> Maybe [TxOut] -> Int -> PsbtMap -> ScriptOutput -> Either String (Maybe (ByteString, WitnessStack))+finalTaprootScripts ctx tx allCoins i m coinScript = do+  program <- taprootProgram coinScript+  outKey <- maybe (Left "the coin is not a Taproot output") Right (importXOnlyPubKey ctx program)+  sighashM <- sigHashType m+  keySigM <- tapKeySig m+  scriptSigs <- tapScriptSigs ctx m+  leaves <- tapLeafScripts ctx m+  let sameType :: TaprootSignature -> Either String ()+      sameType signature = forM_ sighashM $ \asked ->+        unless (signature.hash == asked) $ Left "a Taproot signature does not use the sighash type of the input"+  case keySigM of+    Just signature -> do+      sameType signature+      spent <- maybe (Left "a Taproot signature needs the output every input spends") Right allCoins+      digest <- taprootSigHash tx spent i signature.hash Nothing Nothing+      unless (verifySchnorr ctx outKey signature.sig (hashToMsg digest)) $+        Left "the key path signature does not verify"+      return (Just (B.empty, [encodeTaprootSig signature]))+    Nothing -> do+      let candidates =+            [ (leaf, leafKey, signature)+            | leaf <- leaves,+              Just leafKey <- [singleKeyLeaf ctx leaf],+              Just signature <- [lookup (leafKey, leafHash leaf.leafVersion leaf.script) [((k, h), sig) | ((k, h), sig) <- scriptSigs, sameX k leafKey]]+            ]+      case candidates of+        [] -> return Nothing+        (leaf, leafKey, signature) : _ -> do+          sameType signature+          spent <- maybe (Left "a Taproot signature needs the output every input spends") Right allCoins+          (v, internalKey, proof) <- decodeControlBlock ctx leaf.controlBlock+          let spd = ScriptPathData Nothing [] leaf.script (odd v) (v .&. 0xfe) internalKey proof+          unless (verifyScriptPathData ctx outKey spd) $+            Left "the control block of the leaf does not commit it to the output key"+          digest <- taprootSigHash tx spent i signature.hash Nothing (Just (leafHash leaf.leafVersion leaf.script, 0xffffffff))+          unless (verifySchnorr ctx leafKey signature.sig (hashToMsg digest)) $+            Left "the script path signature does not verify"+          return (Just (B.empty, [encodeTaprootSig signature, S.encode leaf.script, leaf.controlBlock]))+  where+    sameX a b = exportXOnlyPubKey ctx a == exportXOnlyPubKey ctx b
src/Haskoin/Transaction/Segwit.hs view
@@ -59,7 +59,7 @@   = P2WPKH WitnessProgramPKH   | P2WSH WitnessProgramSH   | EmptyWitnessProgram-  deriving (Eq)+  deriving (Eq, Show)  -- | Encode a witness program --@@ -80,7 +80,7 @@   { signature :: !TxSignature,     key :: !PublicKey   }-  deriving (Eq)+  deriving (Eq, Show)  -- | High-level representation of a P2WSH witness --@@ -103,7 +103,7 @@ viewWitnessProgram net ctx so witness = case so of   PayWitnessPKHash _ | length witness == 2 -> do     sig <- decodeTxSig net ctx (head witness)-    pubkey <- unmarshal ctx $ witness !! 1+    pubkey <- decodePubKey ctx $ witness !! 1     return . P2WPKH $ WitnessProgramPKH sig pubkey   PayWitnessScriptHash _ | not (null witness) -> do     redeemScript <- runGetS deserialize $ last witness@@ -130,7 +130,7 @@       (PayPKHash _, [sigBS, keyBS]) ->         SpendPKHash           <$> decodeTxSig net ctx sigBS-          <*> unmarshal ctx keyBS+          <*> decodePubKey ctx keyBS       (PayMulSig _ _, "" : sigsBS) ->         SpendMulSig           <$> traverse (decodeTxSig net ctx) sigsBS@@ -152,15 +152,15 @@   (PayScriptHash {}, RegularInput (SpendPKHash sig pk)) ->     Right $ p2wpkh sig pk   (PayWitnessScriptHash {}, ScriptHashInput i o) ->-    Right $ p2wsh i o+    p2wsh i o   (PayScriptHash {}, ScriptHashInput i o) ->-    Right $ p2wsh i o+    p2wsh i o   _ -> Left "calcWitnessProgram: Invalid segwit SigInput"   where     p2wpkh sig =       P2WPKH . WitnessProgramPKH sig-    p2wsh i =-      P2WSH . WitnessProgramSH (simpleInputStack net ctx i) . encodeOutput ctx+    p2wsh i o =+      P2WSH . WitnessProgramSH (simpleInputStack net ctx i) <$> encodeOutput ctx o  -- | Create the witness stack required to spend a standard P2WSH input --
src/Haskoin/Transaction/Taproot.hs view
@@ -17,27 +17,52 @@ -- Stability   : experimental -- Portability : POSIX ----- This module provides support for reperesenting full taproot outputs and parsing--- taproot witnesses.  For reference see BIPS 340, 341, and 342.+-- Taproot outputs, witnesses and signatures: the script trees and output+-- keys of BIP 341, the witness of a key path or script path spend, the+-- control block, and the digest a Taproot signature signs. For reference+-- see BIPs 340, 341 and 342. module Haskoin.Transaction.Taproot-  ( XOnlyPubKey (..),+  ( -- * Keys+    XOnlyPubKey (..),+    keyParity,++    -- * Script trees     TapLeafVersion,+    tapscriptLeafVersion,     MAST (..),     mastCommitment,     getMerkleProofs,+    leafHash,+    TapLeaf (..),+    tapTreeLeaves,+    tapTreeFromLeaves,++    -- * Outputs     TaprootOutput (..),     taprootOutputKey,+    taprootOutputSecKey,     taprootScriptOutput,++    -- * Witnesses     TaprootWitness (..),     ScriptPathData (..),     viewTaprootWitness,     encodeTaprootWitness,     verifyScriptPathData,+    maxControlBlockNodes,+    encodeControlBlock,+    decodeControlBlock,++    -- * Signatures+    TaprootSignature (..),+    isTaprootSigHash,+    taprootSigHash,+    encodeTaprootSig,+    decodeTaprootSig,   ) where -import Control.Applicative (many)-import Control.Monad ((<=<))+import Control.Monad (forM_, unless, when, (<=<)) import Crypto.Hash   ( Digest,     SHA256,@@ -54,31 +79,33 @@     withText,   ) import Data.Aeson.Types (Parser, Value)-import Data.Binary (Binary (..)) import Data.Bits ((.&.), (.|.)) import Data.Bool (bool) import Data.ByteArray qualified as BA import Data.ByteString (ByteString) import Data.ByteString qualified as BS import Data.Bytes.Get (MonadGet, getBytes, runGetS)-import Data.Bytes.Put (MonadPut, putByteString, runPutL, runPutS)+import Data.Bytes.Put (MonadPut, putByteString, putWord32le, putWord64le, putWord8, runPutL, runPutS) import Data.Bytes.Serial (Serial (..), deserialize, serialize) import Data.Bytes.VarInt (VarInt (VarInt)) import Data.Foldable (foldl')-import Data.Maybe (fromMaybe, mapMaybe)-import Data.Serialize (Serialize, get, getByteString, getWord8, put)-import Data.Word (Word8)+import Data.Maybe (fromMaybe, maybeToList)+import Data.Word (Word32, Word8) import Haskoin.Crypto.Hash import Haskoin.Crypto.Keys.Common-import Haskoin.Crypto.Keys.Extended+import Haskoin.Network.Common (putVarIntBytes) import Haskoin.Script.Common+import Haskoin.Script.SigHash import Haskoin.Script.Standard import Haskoin.Transaction.Common import Haskoin.Util +{- Keys -}+ -- | An x-only pubkey corresponds to the keys @(x,y)@ and @(x, -y)@.  The -- equality test only checks the x-coordinate.  An x-only pubkey serializes to 32--- bytes.+-- bytes, and reads back as the point with the even y coordinate, as BIP 340+-- defines. -- -- @since 0.21.0 newtype XOnlyPubKey = XOnlyPubKey {point :: PubKey}@@ -91,15 +118,11 @@  instance Marshal Ctx XOnlyPubKey where   marshalPut ctx (XOnlyPubKey pk) =-    putByteString-      . BS.drop 1-      . marshal ctx-      $ PublicKey pk True+    putByteString (exportXOnlyPubKey ctx pk)    marshalGet ctx =-    either fail (pure . XOnlyPubKey . (\PublicKey {point} -> point))-      . unmarshal ctx-      . BS.cons 0x02+    maybe (fail "Not an x-only public key") (pure . XOnlyPubKey)+      . importXOnlyPubKey ctx       =<< getBytes 32  instance MarshalJSON Ctx XOnlyPubKey where@@ -115,9 +138,23 @@   marshalEncoding ctx =     hexEncoding . runPutL . marshalPut ctx +-- | The parity of the y coordinate of a key: 0 when it is even, 1 when it+-- is odd. It is the parity bit of a control block.+keyParity :: Ctx -> PubKey -> Word8+keyParity ctx key =+  case BS.unpack . marshal ctx $ PublicKey key True of+    0x02 : _ -> 0x00+    _ -> 0x01++{- Script trees -}+ -- | @since 0.21.0 type TapLeafVersion = Word8 +-- | The leaf version of a Tapscript, @0xc0@, the only one BIP 342 defines.+tapscriptLeafVersion :: TapLeafVersion+tapscriptLeafVersion = 0xc0+ -- | Merklized Abstract Syntax Tree.  This type can represent trees where only a -- subset of the leaves are known.  Note that the tree is invariant under swapping -- branches at an internal node.@@ -127,7 +164,7 @@   = MASTBranch MAST MAST   | MASTLeaf TapLeafVersion Script   | MASTCommitment (Digest SHA256)-  deriving (Show)+  deriving (Eq, Show)  -- | Get the inclusion proofs for the leaves in the tree.  The proof is ordered -- leaf-to-root.@@ -164,6 +201,8 @@         max hashA hashB       ] +-- | The hash of a leaf of the tree, @TapLeaf@ of BIP 341: the tagged hash+-- of the leaf version and the script with its length. leafHash :: TapLeafVersion -> Script -> Digest SHA256 leafHash leafVersion leafScript =   hashFinalize@@ -176,7 +215,52 @@   where     scriptBytes = runPutS $ serialize leafScript --- | Representation of a full taproot output.+-- | A leaf of a script tree with its depth, as the field+-- @PSBT_OUT_TAP_TREE@ of BIP 371 lists the leaves, depth first+data TapLeaf = TapLeaf+  { depth :: !Word8,+    version :: !TapLeafVersion,+    script :: !Script+  }+  deriving (Eq, Show)++-- | The leaves of a tree, depth first, with their depth. 'Nothing' when a+-- branch of the tree is only known by its commitment, since its leaves are+-- not known.+tapTreeLeaves :: MAST -> Maybe [TapLeaf]+tapTreeLeaves = go 0+  where+    go d = \case+      MASTBranch l r -> (<>) <$> go (d + 1) l <*> go (d + 1) r+      MASTLeaf v s -> Just [TapLeaf d v s]+      MASTCommitment _ -> Nothing++-- | The tree whose leaves these are, depth first with their depth, built+-- as @TaprootBuilder@ builds it in Bitcoin Core: two nodes at the same+-- depth join into their parent. 'Nothing' when the leaves do not make one+-- whole tree, or when a depth is above 128.+tapTreeFromLeaves :: [TapLeaf] -> Maybe MAST+tapTreeFromLeaves = go []+  where+    go :: [(Word8, MAST)] -> [TapLeaf] -> Maybe MAST+    go [(0, tree)] [] = Just tree+    go _ [] = Nothing+    go stack (leaf : rest)+      | leaf.depth > 128 = Nothing+      | otherwise = go (merge ((leaf.depth, MASTLeaf leaf.version leaf.script) : stack)) rest+    -- The newest node is first. Two nodes at the same depth become a+    -- branch at the depth above, the older one on the left.+    merge :: [(Word8, MAST)] -> [(Word8, MAST)]+    merge ((d1, right) : (d2, left) : more)+      | d1 == d2 && d1 > 0 = merge ((d1 - 1, MASTBranch left right) : more)+    merge stack = stack++{- Outputs -}++-- | Representation of a full taproot output: the internal key and the+-- script tree, if there is one. Only the x coordinate of the internal key+-- matters, as BIP 341 defines the output from the x-only internal key: a+-- key and its negation give the same output. -- -- @since 0.21.0 data TaprootOutput = TaprootOutput@@ -184,26 +268,40 @@     mast :: Maybe MAST   } --- | @since 0.21.0+-- | The output key of BIP 341: the x-only internal key tweaked with the+-- hash of the internal key and the root of the tree. The parity of its y+-- coordinate is the one a control block of a script path spend carries.+--+-- @since 0.21.0 taprootOutputKey :: Ctx -> TaprootOutput -> PubKey taprootOutputKey ctx TaprootOutput {..} =   fromMaybe keyFail $-    tweak commitment >>= tweakAddPubKey ctx internalKey+    tweak commitment >>= tweakAddXOnlyPubKey ctx internalKey   where     commitment =       taprootCommitment ctx internalKey $         mastCommitment <$> mast     keyFail = error "haskoin-core taprootOutputKey: key derivation failed" +-- | The secret key that signs a key path spend of the output of the secret+-- key given: the secret key, negated when its public key has an odd y+-- coordinate, plus the tweak of 'taprootOutputKey'. 'Nothing' in the+-- negligible case where the tweak gives no key.+taprootOutputSecKey :: Ctx -> SecKey -> Maybe MAST -> Maybe SecKey+taprootOutputSecKey ctx secret mast =+  tweak commitment >>= tweakAddXOnlySecKey ctx secret+  where+    commitment =+      taprootCommitment ctx (derivePubKey ctx secret) $+        mastCommitment <$> mast+ taprootCommitment :: Ctx -> PubKey -> Maybe (Digest SHA256) -> ByteString taprootCommitment ctx internalKey merkleRoot =   BA.convert     . hashFinalize     . maybe id (flip hashUpdate) merkleRoot-    . (`hashUpdate` keyBytes)+    . (`hashUpdate` exportXOnlyPubKey ctx internalKey)     $ initTaggedHash "TapTweak"-  where-    keyBytes = runPutS . marshalPut ctx $ XOnlyPubKey internalKey  -- | Generate the output script for a taproot output --@@ -216,72 +314,117 @@     . XOnlyPubKey     . taprootOutputKey ctx +{- Witnesses -}+ -- | Comprehension of taproot witness data -- -- @since 0.21.0 data TaprootWitness-  = -- | Signature-    KeyPathSpend ByteString+  = -- | A key path spend: the signature, and the annex if there is one+    KeyPathSpend ByteString (Maybe ByteString)   | ScriptPathSpend ScriptPathData-  deriving (Eq)+  deriving (Eq, Show) --- | @since 0.21.0+-- | A script path spend: the items the script runs on, the script, and+-- the control block that commits the script to the output key.+--+-- @since 0.21.0 data ScriptPathData = ScriptPathData   { annex :: Maybe ByteString,     stack :: [ByteString],     script :: Script,+    -- | the parity of the y coordinate of the output key     extIsOdd :: Bool,     -- | This value is masked by 0xFE     leafVersion :: Word8,     internalKey :: PubKey,-    control :: [ByteString]+    -- | the Merkle proof of the leaf, leaf to root+    control :: [Digest SHA256]   }-  deriving (Eq)+  deriving (Eq, Show) --- | Try to interpret a 'WitnessStack' as taproot witness data.+-- | The number of nodes a control block may hold at most, the depth of the+-- deepest leaf BIP 341 allows+maxControlBlockNodes :: Int+maxControlBlockNodes = 128++-- | The control block of a script path spend: the leaf version with the+-- parity bit of the output key, the x-only internal key, and the proof+-- of the leaf+encodeControlBlock :: Ctx -> Word8 -> PubKey -> [Digest SHA256] -> ByteString+encodeControlBlock ctx versionAndParity internalKey proof =+  BS.concat (BS.singleton versionAndParity : exportXOnlyPubKey ctx internalKey : map BA.convert proof)++-- | The parts of a control block, which must be 33 bytes plus 32 per node+-- of the proof, with at most 'maxControlBlockNodes' nodes, as BIP 341+-- requires+decodeControlBlock :: Ctx -> ByteString -> Either String (Word8, PubKey, [Digest SHA256])+decodeControlBlock ctx bytes+  | len < 33 || (len - 33) `mod` 32 /= 0 =+      Left ("decodeControlBlock: a control block is 33 bytes plus 32 per node, not " <> show len)+  | nodeCount > maxControlBlockNodes =+      Left ("decodeControlBlock: more than " <> show maxControlBlockNodes <> " nodes")+  | otherwise = do+      internalKey <- maybe (Left "decodeControlBlock: the internal key is not an x-only public key") Right $ importXOnlyPubKey ctx (BS.take 32 (BS.drop 1 bytes))+      proof <- traverse node (chunks (BS.drop 33 bytes))+      return (BS.head bytes, internalKey, proof)+  where+    len = BS.length bytes+    nodeCount = (len - 33) `div` 32+    chunks b+      | BS.null b = []+      | otherwise = BS.take 32 b : chunks (BS.drop 32 b)+    node = maybe (Left "decodeControlBlock: not a hash") Right . digestFromByteString @SHA256++-- | Try to interpret a 'WitnessStack' as taproot witness data, as BIP 341+-- reads it: the last item is the annex when there are at least two items+-- and it starts with @0x50@; then one item left is a key path signature,+-- and more are the stack, the script and the control block. -- -- @since 0.21.0 viewTaprootWitness :: Ctx -> WitnessStack -> Maybe TaprootWitness-viewTaprootWitness ctx witnessStack = case reverse witnessStack of-  [sig] -> Just $ KeyPathSpend sig-  annexA : remainingStack-    | 0x50 : _ <- BS.unpack annexA ->-        parseSpendPathData (Just annexA) remainingStack-  remainingStack -> parseSpendPathData Nothing remainingStack+viewTaprootWitness ctx witnessStack =+  case reverse items of+    [sig] -> Just (KeyPathSpend sig annex)+    controlBytes : scriptBytes : reversedStack -> do+      script <- eitherToMaybe $ runGetS deserialize scriptBytes+      (v, internalKey, control) <- eitherToMaybe $ decodeControlBlock ctx controlBytes+      pure $+        ScriptPathSpend+          ScriptPathData+            { annex,+              stack = reverse reversedStack,+              script,+              extIsOdd = odd v,+              leafVersion = v .&. 0xfe,+              internalKey,+              control+            }+    [] -> Nothing   where-    parseSpendPathData annex = \case-      scriptBytes : controlBytes : stack -> do-        script <- eitherToMaybe $ runGetS deserialize scriptBytes-        (v, internalKey, control) <- deconstructControl controlBytes-        let extIsOdd = odd v-            leafVersion = v .&. 0xFE-        pure $ ScriptPathSpend ScriptPathData {..}-      _ -> Nothing-    deconstructControl = eitherToMaybe . runGetS deserializeControl-    deserializeControl = do-      v <- getWord8-      XOnlyPubKey k <- marshalGet ctx-      proof <- many $ getByteString 32-      pure (v, k, proof)+    (annex, items) = case reverse witnessStack of+      a : rest+        | not (null rest),+          Just (0x50, _) <- BS.uncons a ->+            (Just a, reverse rest)+      _ -> (Nothing, witnessStack)  -- | Transform the high-level representation of taproot witness data into a witness stack -- -- @since 0.21.0 encodeTaprootWitness :: Ctx -> TaprootWitness -> WitnessStack encodeTaprootWitness ctx = \case-  KeyPathSpend signature -> pure signature-  ScriptPathSpend scriptPathData -> wit scriptPathData-  where-    wit d = (.stack) d <> [script d, keys d, annex d]-    keys d = mconcat [verpar d, xonlyk d, ctrl d]-    script = runPutS . serialize . (.script)-    verpar d = BS.pack [(.leafVersion) d .|. parity d]-    xonlyk = runPutS . marshalPut ctx . XOnlyPubKey . (.internalKey)-    annex = fromMaybe mempty . (.annex)-    ctrl = mconcat . (.control)-    parity = bool 0 1 . (.extIsOdd)+  KeyPathSpend signature annex -> signature : maybeToList annex+  ScriptPathSpend d ->+    d.stack+      <> [ runPutS (serialize d.script),+           encodeControlBlock ctx (d.leafVersion .|. bool 0 1 d.extIsOdd) d.internalKey d.control+         ]+      <> maybeToList d.annex --- | Verify that the script path spend is valid, except for script execution.+-- | Verify that the script path spend is valid, except for script execution:+-- the leaf and its proof commit to the output key, with the parity the+-- control block says. -- -- @since 0.21.0 verifyScriptPathData ::@@ -293,23 +436,123 @@ verifyScriptPathData ctx outkey spd = fromMaybe False $ do   tweak commitment     >>= fmap onComputedKey-      . tweakAddPubKey ctx spd.internalKey+      . tweakAddXOnlyPubKey ctx spd.internalKey   where     onComputedKey computedKey =       XOnlyPubKey outkey == XOnlyPubKey computedKey         && expectedParity == keyParity ctx computedKey     commitment =       taprootCommitment ctx spd.internalKey (Just merkleRoot)-    merkleRoot =-      foldl' hashBranch theLeafHash $-        mapMaybe (digestFromByteString @SHA256) spd.control-    theLeafHash =-      (leafHash <$> (.&. 0xFE) . (.leafVersion) <*> (.script))-        spd+    merkleRoot = foldl' hashBranch theLeafHash spd.control+    theLeafHash = leafHash (spd.leafVersion .&. 0xfe) spd.script     expectedParity = bool 0 1 spd.extIsOdd -keyParity :: Ctx -> PubKey -> Word8-keyParity ctx key =-  case BS.unpack . marshal ctx $ PublicKey key True of-    0x02 : _ -> 0x00-    _ -> 0x01+{- Signatures -}++-- | A Schnorr signature with the hash type it signs: 64 bytes, then the+-- hash type byte unless it is 'sigHashDefault'+data TaprootSignature = TaprootSignature+  { sig :: !SchnorrSig,+    hash :: !SigHash+  }+  deriving (Eq, Show)++-- | Whether a hash type is one a Taproot signature may use: the default,+-- @ALL@, @NONE@ and @SINGLE@, the last three with or without+-- @ANYONECANPAY@. Bitcoin Core refuses every other value in+-- @SignatureHashSchnorr@.+isTaprootSigHash :: SigHash -> Bool+isTaprootSigHash sh = sh `elem` [0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83]++-- | The bytes of a Taproot signature as they go in a witness: the 64 bytes+-- of the signature, then the hash type byte unless it is 'sigHashDefault'+encodeTaprootSig :: TaprootSignature -> ByteString+encodeTaprootSig s+  | s.hash == sigHashDefault = s.sig.get+  | otherwise = s.sig.get <> BS.singleton (fromIntegral s.hash)++-- | A Taproot signature from its bytes: 64 bytes sign with+-- 'sigHashDefault', 65 end with a hash type byte that must be one+-- 'isTaprootSigHash' accepts and not zero, as BIP 341 requires+decodeTaprootSig :: ByteString -> Either String TaprootSignature+decodeTaprootSig bytes+  | BS.length bytes == 64 = (`TaprootSignature` sigHashDefault) <$> sigOf bytes+  | BS.length bytes == 65 = do+      let sh = fromIntegral (BS.last bytes)+      unless (isTaprootSigHash sh && sh /= sigHashDefault) $+        Left ("decodeTaprootSig: invalid hash type byte " <> show (BS.last bytes))+      (`TaprootSignature` sh) <$> sigOf (BS.init bytes)+  | otherwise = Left ("decodeTaprootSig: a signature is 64 or 65 bytes, not " <> show (BS.length bytes))+  where+    sigOf = maybe (Left "decodeTaprootSig: not a Schnorr signature") Right . schnorrSig++-- | The digest a Taproot signature signs, as BIP 341 and BIP 342 define it:+-- the tagged hash of the signature message, which commits to the whole+-- transaction and to the output every input spends, and, for a script path+-- spend, to the leaf. Fails when the hash type is not one of+-- 'isTaprootSigHash', when the input index is out of range, when the+-- spent outputs do not match the inputs, or with @SIGHASH_SINGLE@ and no+-- output at the index of the input.+taprootSigHash ::+  -- | transaction to sign+  Tx ->+  -- | outputs spent, one per input of the transaction, in order+  [TxOut] ->+  -- | index of input being signed+  Int ->+  -- | what to sign+  SigHash ->+  -- | annex of the input, if there is one+  Maybe ByteString ->+  -- | for a script path spend: the leaf hash, and the position of the last+  -- @OP_CODESEPARATOR@ executed, @0xffffffff@ for none+  Maybe (Digest SHA256, Word32) ->+  Either String Hash256+taprootSigHash tx spent i sh annexM leafM = do+  unless (i >= 0 && i < length tx.inputs) $+    Left "taprootSigHash: input index out of range"+  unless (length spent == length tx.inputs) $+    Left "taprootSigHash: one spent output per input is required"+  unless (isTaprootSigHash sh) $+    Left ("taprootSigHash: invalid hash type " <> show (fromIntegral sh :: Word32))+  when (isSigHashSingle sh && i >= length tx.outputs) $+    Left "taprootSigHash: SIGHASH_SINGLE without an output at the input index"+  return . taggedHash "TapSighash" . runPutS $ do+    putWord8 0x00 -- the epoch+    putWord8 (fromIntegral sh)+    putWord32le tx.version+    putWord32le tx.locktime+    unless (anyoneCanPay sh) $ do+      serialize $ sha256 (runPutS (mapM_ (serialize . (.outpoint)) tx.inputs))+      serialize $ sha256 (runPutS (mapM_ (putWord64le . (.value)) spent))+      serialize $ sha256 (runPutS (mapM_ (putVarIntBytes . (.script)) spent))+      serialize $ sha256 (runPutS (mapM_ (putWord32le . (.sequence)) tx.inputs))+    unless (isSigHashNone sh || isSigHashSingle sh) $+      serialize $ sha256 (runPutS (mapM_ serialize tx.outputs))+    putWord8 (bool 0 2 (isJust' leafM) .|. bool 0 1 (isJust' annexM))+    if anyoneCanPay sh+      then do+        serialize input.outpoint+        putWord64le coin.value+        putVarIntBytes coin.script+        putWord32le input.sequence+      else putWord32le (fromIntegral i)+    forM_ annexM $ \annex ->+      serialize $ sha256 (runPutS (putVarIntBytes annex))+    when (isSigHashSingle sh) $+      serialize $ sha256 (runPutS (serialize (tx.outputs !! i)))+    forM_ leafM $ \(leaf, codeSeparatorPosition) -> do+      putByteString (BA.convert leaf)+      putWord8 0x00 -- the key version+      putWord32le codeSeparatorPosition+  where+    input = tx.inputs !! i+    coin = spent !! i+    isJust' = maybe False (const True)++-- | The tagged hash of BIP 340: SHA256 of the hash of the tag twice, then+-- the message+taggedHash :: ByteString -> ByteString -> Hash256+taggedHash tag message = sha256 (tagHash <> tagHash <> message)+  where+    tagHash = runPutS (serialize (sha256 tag))
src/Haskoin/Util/Arbitrary/Address.hs view
@@ -68,7 +68,7 @@  -- | Arbitrary pay-to-witness script hash arbitraryWitnessScriptAddress :: Gen Address-arbitraryWitnessScriptAddress = witnessPubKeyAddress <$> arbitraryHash160+arbitraryWitnessScriptAddress = witnessScriptAddress <$> arbitraryHash256  arbitraryWitnessAddress :: Gen Address arbitraryWitnessAddress = do
src/Haskoin/Util/Arbitrary/Keys.hs view
@@ -37,15 +37,14 @@ arbitraryFingerprint :: Gen Fingerprint arbitraryFingerprint = Fingerprint <$> arbitrary --- | Arbitrary extended private key.+-- | Arbitrary extended private key. A key at depth 0 is a master key, and+-- BIP32 requires it to have a zero parent fingerprint and index. arbitraryXPrvKey :: Gen XPrvKey-arbitraryXPrvKey =-  XPrvKey-    <$> arbitrary-    <*> arbitraryFingerprint-    <*> arbitrary-    <*> arbitraryHash256-    <*> arbitrary+arbitraryXPrvKey = do+  depth <- arbitrary+  parent <- if depth == 0 then pure (Fingerprint 0) else arbitraryFingerprint+  index <- if depth == 0 then pure 0 else arbitrary+  XPrvKey depth parent index <$> arbitraryHash256 <*> arbitrary  -- | Arbitrary extended public key. arbitraryXPubKey :: Ctx -> Gen XPubKey
src/Haskoin/Util/Arbitrary/Message.hs view
@@ -7,13 +7,18 @@ -- Portability : POSIX module Haskoin.Util.Arbitrary.Message where +import Data.List (nub, sort)+import Data.Word (Word32) import Haskoin.Crypto (Ctx)+import Haskoin.Network.BlockFilter+import Haskoin.Network.CompactBlock import Haskoin.Network.Data import Haskoin.Network.Message import Haskoin.Util.Arbitrary.Block import Haskoin.Util.Arbitrary.Crypto import Haskoin.Util.Arbitrary.Network import Haskoin.Util.Arbitrary.Transaction+import Haskoin.Util.Arbitrary.Util import Test.QuickCheck  -- | Arbitrary 'MessageHeader'.@@ -25,13 +30,70 @@     <*> arbitrary     <*> arbitraryCheckSum32 --- | Arbitrary 'Message'.+-- | Arbitrary 'SendCompact'.+arbitrarySendCompact :: Gen SendCompact+arbitrarySendCompact = SendCompact <$> arbitrary <*> elements [1, 2]++-- | Strictly increasing block positions.+arbitraryPositions :: Gen [Word32]+arbitraryPositions = nub . sort <$> listOf arbitrary++-- | Arbitrary 'CompactBlock'.+arbitraryCompactBlock :: Network -> Ctx -> Gen CompactBlock+arbitraryCompactBlock net ctx = do+  positions <- arbitraryPositions+  CompactBlock+    <$> arbitraryBlockHeader+    <*> arbitrary+    <*> listOf (choose (0, maxShortId))+    <*> mapM (\i -> PrefilledTx i <$> arbitraryTx net ctx) positions++-- | Arbitrary 'GetBlockTxn'.+arbitraryGetBlockTxn :: Gen GetBlockTxn+arbitraryGetBlockTxn = GetBlockTxn <$> arbitraryBlockHash <*> arbitraryPositions++-- | Arbitrary 'BlockTxn'.+arbitraryBlockTxn :: Network -> Ctx -> Gen BlockTxn+arbitraryBlockTxn net ctx = BlockTxn <$> arbitraryBlockHash <*> listOf (arbitraryTx net ctx)++-- | Arbitrary 'GetCFilters'.+arbitraryGetCFilters :: Gen GetCFilters+arbitraryGetCFilters = GetCFilters <$> arbitrary <*> arbitrary <*> arbitraryBlockHash++-- | Arbitrary 'CFilter'.+arbitraryCFilter :: Gen CFilter+arbitraryCFilter = CFilter <$> arbitrary <*> arbitraryBlockHash <*> arbitraryBS++-- | Arbitrary 'GetCFHeaders'.+arbitraryGetCFHeaders :: Gen GetCFHeaders+arbitraryGetCFHeaders = GetCFHeaders <$> arbitrary <*> arbitrary <*> arbitraryBlockHash++-- | Arbitrary 'CFHeaders'.+arbitraryCFHeaders :: Gen CFHeaders+arbitraryCFHeaders =+  CFHeaders+    <$> arbitrary+    <*> arbitraryBlockHash+    <*> arbitraryHash256+    <*> listOf arbitraryHash256++-- | Arbitrary 'GetCFCheckpt'.+arbitraryGetCFCheckpt :: Gen GetCFCheckpt+arbitraryGetCFCheckpt = GetCFCheckpt <$> arbitrary <*> arbitraryBlockHash++-- | Arbitrary 'CFCheckpt'.+arbitraryCFCheckpt :: Gen CFCheckpt+arbitraryCFCheckpt = CFCheckpt <$> arbitrary <*> arbitraryBlockHash <*> listOf arbitraryHash256++-- | Arbitrary 'Message' of every kind, including one of an unknown command. arbitraryMessage :: Network -> Ctx -> Gen Message arbitraryMessage net ctx =   oneof     [ MVersion <$> arbitraryVersion,       return MVerAck,       MAddr <$> arbitraryAddr1,+      MAddrV2 <$> arbitraryAddrV2,+      return MSendAddrV2,       MInv <$> arbitraryInv1,       MGetData <$> arbitraryGetData,       MNotFound <$> arbitraryNotFound,@@ -48,6 +110,21 @@       MPing <$> arbitraryPing,       MPong <$> arbitraryPong,       MAlert <$> arbitraryAlert,+      return MMempool,       MReject <$> arbitraryReject,-      return MSendHeaders+      return MSendHeaders,+      MFeeFilter <$> arbitraryFeeFilter,+      MSendCompact <$> arbitrarySendCompact,+      MCompactBlock <$> arbitraryCompactBlock net ctx,+      MGetBlockTxn <$> arbitraryGetBlockTxn,+      MBlockTxn <$> arbitraryBlockTxn net ctx,+      MGetCFilters <$> arbitraryGetCFilters,+      MCFilter <$> arbitraryCFilter,+      MGetCFHeaders <$> arbitraryGetCFHeaders,+      MCFHeaders <$> arbitraryCFHeaders,+      MGetCFCheckpt <$> arbitraryGetCFCheckpt,+      MCFCheckpt <$> arbitraryCFCheckpt,+      return MWtxidRelay,+      MSendTxRcncl <$> arbitrarySendTxRcncl,+      MOther <$> arbitraryOtherCommand <*> arbitraryBS     ]
src/Haskoin/Util/Arbitrary/Network.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}+ -- | -- Module      : Haskoin.Test.Network -- Copyright   : No rights reserved@@ -7,8 +10,10 @@ -- Portability : POSIX module Haskoin.Util.Arbitrary.Network where -import qualified Data.ByteString as BS (empty, pack)-import qualified Data.ByteString.Char8 as C8+import Data.Bits (shiftL)+import Data.ByteString qualified as BS+import Data.ByteString.Char8 qualified as C8+import Data.Sequence qualified as Seq import Data.Word (Word16, Word32) import Haskoin.Network import Haskoin.Util.Arbitrary.Crypto@@ -46,9 +51,51 @@ arbitraryNetworkAddressTime :: Gen (Word32, NetworkAddress) arbitraryNetworkAddressTime = (,) <$> arbitrary <*> arbitraryNetworkAddress --- | Arbitrary 'InvType'.+-- | Arbitrary 'AddressNetwork', known or unknown.+arbitraryAddressNetwork :: Gen AddressNetwork+arbitraryAddressNetwork =+  frequency+    [ (7, elements [AddrIPv4, AddrIPv6, AddrTorV2, AddrTorV3, AddrI2P, AddrCJDNS, AddrYggdrasil]),+      (1, AddrUnknown <$> choose (8, 255))+    ]++-- | Arbitrary 'NetworkAddressV2' with as many address bytes as its network+-- requires. An unknown network gets up to 'maxAddrV2Size' bytes.+arbitraryNetworkAddressV2 :: Gen NetworkAddressV2+arbitraryNetworkAddressV2 = do+  network <- arbitraryAddressNetwork+  len <- maybe (choose (0, fromIntegral maxAddrV2Size)) return (addressNetworkLength network)+  NetworkAddressV2+    <$> arbitrary+    <*> arbitrary+    <*> pure network+    <*> arbitraryBSn len+    <*> arbitrary++-- | Arbitrary 'AddrV2'.+arbitraryAddrV2 :: Gen AddrV2+arbitraryAddrV2 = AddrV2 <$> listOf arbitraryNetworkAddressV2++-- | Arbitrary 'InvType', including unknown types. arbitraryInvType :: Gen InvType-arbitraryInvType = elements [InvError, InvTx, InvBlock, InvMerkleBlock]+arbitraryInvType =+  frequency+    [ ( 9,+        elements+          [ InvError,+            InvTx,+            InvBlock,+            InvMerkleBlock,+            InvCompactBlock,+            InvWTx,+            InvWitnessTx,+            InvWitnessBlock,+            InvWitnessMerkleBlock+          ]+      ),+      -- Types between the last plain one and the witness flag are unknown.+      (1, InvType <$> choose (6, 1 `shiftL` 30))+    ]  -- | Arbitrary 'InvVector'. arbitraryInvVector :: Gen InvVector@@ -58,7 +105,7 @@ arbitraryInv1 :: Gen Inv arbitraryInv1 = Inv <$> listOf1 arbitraryInvVector --- | Arbitrary 'Version'.+-- | Arbitrary 'Version' with a user agent that fits 'maxUserAgentLength'. arbitraryVersion :: Gen Version arbitraryVersion =   Version@@ -68,7 +115,7 @@     <*> arbitraryNetworkAddress     <*> arbitraryNetworkAddress     <*> arbitrary-    <*> arbitraryVarString+    <*> (VarString <$> (arbitraryBSn =<< choose (0, maxUserAgentLength)))     <*> arbitrary     <*> arbitrary @@ -81,26 +128,30 @@ arbitraryAlert :: Gen Alert arbitraryAlert = Alert <$> arbitraryVarString <*> arbitraryVarString +-- | Arbitrary 'FeeFilter'.+arbitraryFeeFilter :: Gen FeeFilter+arbitraryFeeFilter = FeeFilter <$> arbitrary++-- | Arbitrary 'SendTxRcncl'.+arbitrarySendTxRcncl :: Gen SendTxRcncl+arbitrarySendTxRcncl = SendTxRcncl <$> arbitrary <*> arbitrary+ -- | Arbitrary 'Reject'. arbitraryReject :: Gen Reject-arbitraryReject = do-  m <- arbitraryMessageCommand-  c <- arbitraryRejectCode-  s <- arbitraryVarString-  d <--    oneof-      [ return BS.empty,-        BS.pack <$> vectorOf 32 arbitrary-      ]-  return $ Reject m c s d+arbitraryReject =+  Reject+    <$> arbitraryMessageCommand+    <*> arbitraryRejectCode+    <*> arbitraryVarString+    <*> arbitraryMaybe arbitraryHash256 --- | Arbitrary 'RejectCode'.+-- | Arbitrary 'RejectCode', any of the eight. arbitraryRejectCode :: Gen RejectCode arbitraryRejectCode =   elements     [ RejectMalformed,       RejectInvalid,-      RejectInvalid,+      RejectObsolete,       RejectDuplicate,       RejectNonStandard,       RejectDust,@@ -133,49 +184,82 @@       BloomUpdateP2PubKeyOnly     ] --- | Arbitrary bloom filter with its corresponding number of elements--- and false positive rate.-arbitraryBloomFilter :: Gen (Int, Double, BloomFilter)+-- | Arbitrary bloom filter with random bits, within the limits a peer+-- accepts.+arbitraryBloomFilter :: Gen BloomFilter arbitraryBloomFilter = do-  n <- choose (0, 100000)-  fp <- choose (1e-8, 1)-  tweak <- arbitrary-  fl <- arbitraryBloomFlags-  return (n, fp, bloomCreate n fp tweak fl)+  len <- choose (0, 1000)+  bytes <- vectorOf len arbitrary+  BloomFilter (Seq.fromList bytes)+    <$> choose (0, maxHashFuncs)+    <*> arbitrary+    <*> arbitraryBloomFlags  -- | Arbitrary 'FilterLoad'. arbitraryFilterLoad :: Gen FilterLoad-arbitraryFilterLoad = do-  (_, _, bf) <- arbitraryBloomFilter-  return $ FilterLoad bf+arbitraryFilterLoad = FilterLoad <$> arbitraryBloomFilter --- | Arbitrary 'FilterAdd'.+-- | Arbitrary 'FilterAdd' that fits 'maxFilterAddSize'. arbitraryFilterAdd :: Gen FilterAdd-arbitraryFilterAdd = FilterAdd <$> arbitraryBS+arbitraryFilterAdd = FilterAdd <$> (arbitraryBSn =<< choose (0, maxFilterAddSize)) --- | Arbitrary 'MessageCommand'.+-- | Arbitrary 'MessageCommand', known or not. An unknown command has one to+-- twelve printable ASCII characters and is not the name of a known one. arbitraryMessageCommand :: Gen MessageCommand-arbitraryMessageCommand = do-  ASCIIString str <- arbitrary-  elements-    [ MCVersion,-      MCVerAck,-      MCAddr,-      MCInv,-      MCGetData,-      MCNotFound,-      MCGetBlocks,-      MCGetHeaders,-      MCTx,-      MCBlock,-      MCMerkleBlock,-      MCHeaders,-      MCGetAddr,-      MCFilterLoad,-      MCFilterAdd,-      MCFilterClear,-      MCPing,-      MCPong,-      MCAlert,-      MCOther (C8.take 12 (C8.pack (filter (/= '\NUL') str)))+arbitraryMessageCommand =+  frequency+    [ (36, elements knownCommands),+      (4, MCOther <$> arbitraryOtherCommand)     ]+  where+    knownCommands =+      [ MCVersion,+        MCVerAck,+        MCAddr,+        MCAddrV2,+        MCSendAddrV2,+        MCInv,+        MCGetData,+        MCNotFound,+        MCGetBlocks,+        MCGetHeaders,+        MCTx,+        MCBlock,+        MCMerkleBlock,+        MCHeaders,+        MCGetAddr,+        MCFilterLoad,+        MCFilterAdd,+        MCFilterClear,+        MCPing,+        MCPong,+        MCAlert,+        MCMempool,+        MCReject,+        MCSendHeaders,+        MCFeeFilter,+        MCSendCmpct,+        MCCmpctBlock,+        MCGetBlockTxn,+        MCBlockTxn,+        MCGetCFilters,+        MCCFilter,+        MCGetCFHeaders,+        MCCFHeaders,+        MCGetCFCheckpt,+        MCCFCheckpt,+        MCWtxidRelay,+        MCSendTxRcncl+      ]++-- | Arbitrary command name that no known command uses: one to twelve+-- printable ASCII characters.+arbitraryOtherCommand :: Gen BS.ByteString+arbitraryOtherCommand = name `suchThat` unknown+  where+    name = do+      n <- choose (1, 12)+      C8.pack <$> vectorOf n (choose (' ', '~'))+    unknown s = case stringToCommand s of+      MCOther _ -> True+      _ -> False
src/Haskoin/Util/Arbitrary/Script.hs view
@@ -33,12 +33,29 @@ arbitraryScript :: Gen Script arbitraryScript = Script <$> listOf arbitraryScriptOp +-- | Arbitrary push of random data. Most pushes are minimal, see+-- 'opPushData'. Some use a longer 'PushDataType' than the data needs, and the+-- lengths reach every 'PushDataType', so that every encoding is exercised.+arbitraryPushOp :: Gen ScriptOp+arbitraryPushOp = do+  len <- frequency [(6, choose (0, 75)), (2, choose (76, 255)), (1, choose (256, 300))]+  bs <- arbitraryBSn len+  let -- An empty payload with OPCODE is the byte of OP_0, so it is not kept.+      types =+        [OPCODE | len >= 1, len <= 75]+          ++ [OPDATA1 | len <= 255]+          ++ [OPDATA2, OPDATA4]+  frequency+    [ (3, return $ opPushData bs),+      (1, OP_PUSHDATA bs <$> elements types)+    ]+ -- | Arbitrary 'ScriptOp' (push operations have random data). arbitraryScriptOp :: Gen ScriptOp arbitraryScriptOp =   oneof     -- Pushing Data-    [ opPushData <$> arbitraryBS1,+    [ arbitraryPushOp,       return OP_0,       return OP_1NEGATE,       return OP_RESERVED,@@ -195,15 +212,15 @@ arbitrarySigHash :: Gen SigHash arbitrarySigHash = fromIntegral <$> (arbitrary :: Gen Word32) --- | Arbitrary valid 'SigHash'.+-- | Arbitrary valid 'SigHash'. On a network with a fork id, the+-- 'SIGHASH_FORKID' flag is always set, since every signature there must+-- carry it. arbitraryValidSigHash :: Network -> Gen SigHash arbitraryValidSigHash net = do   sh <- elements [sigHashAll, sigHashNone, sigHashSingle]-  f1 <--    elements $-      if isJust net.sigHashForkId-        then [id, setForkIdFlag]-        else [id]+  let f1+        | isJust net.sigHashForkId = setForkIdFlag+        | otherwise = id   f2 <- elements [id, setAnyoneCanPay]   return $ f1 $ f2 sh @@ -231,6 +248,7 @@       not $         isSigHashUnknown sh           || isNothing net.sigHashForkId && hasForkIdFlag sh+          || isJust net.sigHashForkId && not (hasForkIdFlag sh)  -- | Arbitrary transaction signature that could also be empty. arbitraryTxSignatureEmpty :: Network -> Ctx -> Gen TxSignature@@ -240,11 +258,11 @@       (10, lst3 <$> arbitraryTxSignature net ctx)     ] --- | Arbitrary m of n parameters.+-- | Arbitrary m of n parameters, with n up to 'maxMultisigKeys'. arbitraryMSParam :: Gen (Int, Int) arbitraryMSParam = do-  m <- choose (1, 16)-  n <- choose (m, 16)+  m <- choose (1, maxMultisigKeys)+  n <- choose (m, maxMultisigKeys)   return (m, n)  -- | Arbitrary 'ScriptOutput' (Can by any valid type).@@ -323,9 +341,10 @@     Just h -> return $ PayScriptHash h     Nothing -> error "Could not get Hash160 from address" --- | Arbitrary 'ScriptOutput' of type 'DataCarrier'.+-- | Arbitrary 'ScriptOutput' of type 'DataCarrier', with any number of+-- pushes, including none and empty ones. arbitraryDCOutput :: Gen ScriptOutput-arbitraryDCOutput = DataCarrier <$> arbitraryBS1+arbitraryDCOutput = DataCarrier <$> listOf arbitraryBS  -- | Arbitrary 'ScriptInput'. arbitraryScriptInput :: Network -> Ctx -> Gen ScriptInput@@ -334,9 +353,16 @@     [ arbitraryPKInput net ctx,       arbitraryPKHashInput net ctx,       arbitraryMSInput net ctx,-      arbitrarySHInput net ctx+      arbitrarySHInput net ctx,+      arbitraryNestedWitnessInput     ] +-- | Arbitrary 'ScriptInput' of type 'NestedWitnessInput', wrapping a+-- 'PayWitnessPKHash' or a 'PayWitnessScriptHash'.+arbitraryNestedWitnessInput :: Gen ScriptInput+arbitraryNestedWitnessInput =+  NestedWitnessInput <$> oneof [arbitraryWPKHashOutput, arbitraryWSHOutput]+ -- | Arbitrary 'ScriptInput' of type 'SpendPK', 'SpendPKHash' or 'SpendMulSig' -- (not 'ScriptHashInput') arbitrarySimpleInput :: Network -> Ctx -> Gen ScriptInput@@ -379,11 +405,21 @@   sigs <- vectorOf m (arbitraryTxSignatureEmpty net ctx)   return $ RegularInput $ SpendMulSig sigs --- | Arbitrary 'ScriptInput' of type 'ScriptHashInput'.+-- | Arbitrary 'ScriptInput' of type 'ScriptHashInput', whose input fits its+-- redeem script: a 'SpendPK' for a 'PayPK', a 'SpendPKHash' for a+-- 'PayPKHash', a 'SpendMulSig' for a 'PayMulSig'. arbitrarySHInput :: Network -> Ctx -> Gen ScriptInput-arbitrarySHInput net ctx = do-  i <- arbitrarySimpleInput net ctx-  ScriptHashInput i.get <$> arbitrarySimpleOutput ctx+arbitrarySHInput net ctx =+  oneof+    [ do+        sig <- arbitraryTxSignatureEmpty net ctx+        ScriptHashInput (SpendPK sig) <$> arbitraryPKOutput ctx,+      do+        sig <- arbitraryTxSignatureEmpty net ctx+        key <- snd <$> arbitraryKeyPair ctx+        ScriptHashInput (SpendPKHash sig key) <$> arbitraryPKHashOutput,+      arbitraryMulSigSHInput net ctx+    ]  -- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a -- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
src/Haskoin/Util/Arbitrary/Transaction.hs view
@@ -14,9 +14,10 @@ import Control.Monad import Data.ByteString qualified as BS import Data.Either (fromRight)-import Data.List (nub, nubBy, permutations)-import Data.Maybe (fromMaybe)-import Data.Word (Word64)+import Data.List (findIndex, nub, nubBy, permutations)+import Data.Maybe (fromMaybe, isNothing)+import Data.Map.Strict qualified as Map+import Data.Word (Word32, Word64) import Haskoin.Address import Haskoin.Crypto (Ctx) import Haskoin.Crypto.Keys.Common@@ -24,6 +25,7 @@ import Haskoin.Network.Data import Haskoin.Script import Haskoin.Transaction+import Haskoin.Transaction.Segwit (isSegwit) import Haskoin.Util import Haskoin.Util.Arbitrary.Crypto import Haskoin.Util.Arbitrary.Keys@@ -31,21 +33,59 @@ import Haskoin.Util.Arbitrary.Util import Test.QuickCheck --- | Wrapped coin value for testing.-newtype TestCoin = TestCoin {getTestCoin :: Word64}+-- | A coin for testing: its value, and the kind of input it is spent with.+data TestCoin = TestCoin+  { getTestCoin :: Word64,+    getTestCoinKind :: InputKind+  }   deriving (Eq, Show)  instance Coin TestCoin where-  coinValue = getTestCoin+  coinValue = (.getTestCoin)+  coinInputKind = (.getTestCoinKind)  -- | Arbitrary transaction hash (for non-existent transaction). arbitraryTxHash :: Gen TxHash arbitraryTxHash = TxHash <$> arbitraryHash256 --- | Arbitrary amount of Satoshi as 'Word64' (Between 1 and 21e14)+-- | Arbitrary amount of Satoshi as 'Word64' (Between 1 and 21e14), spent+-- with an arbitrary kind of input arbitrarySatoshi :: Network -> Gen TestCoin-arbitrarySatoshi net = TestCoin <$> choose (1, net.maxSatoshi)+arbitrarySatoshi net = TestCoin <$> choose (1, net.maxSatoshi) <*> arbitraryInputKind +-- | Arbitrary kind of input, with multisig parameters from+-- 'arbitraryMSParam'+arbitraryInputKind :: Gen InputKind+arbitraryInputKind = do+  (m, n) <- arbitraryMSParam+  elements+    [ InputPK,+      InputPKHash,+      InputUncompressedPKHash,+      InputMulSig m n,+      InputWitnessPKHash,+      InputWitnessMulSig m n,+      InputNestedWitnessPKHash,+      InputNestedWitnessMulSig m n,+      InputTaproot+    ]++-- | Arbitrary kind of output. An 'OutputScript' is up to 10000 bytes, the+-- most a script may have.+arbitraryOutputKind :: Gen OutputKind+arbitraryOutputKind =+  oneof+    [ elements+        [ OutputPKHash,+          OutputScriptHash,+          OutputScript32Hash,+          OutputWitnessPKHash,+          OutputWitnessScriptHash,+          OutputTaproot+        ],+      OutputScript <$> choose (0, 10000)+    ]+ -- | Arbitrary 'OutPoint'. arbitraryOutPoint :: Gen OutPoint arbitraryOutPoint = OutPoint <$> arbitraryTxHash <*> arbitrary@@ -74,7 +114,9 @@ arbitraryLegacyTx :: Network -> Ctx -> Gen Tx arbitraryLegacyTx net ctx = arbitraryWLTx net ctx False --- | Arbitrary witness transaction (witness data is fake).+-- | Arbitrary witness transaction (witness data is fake). At least one+-- input has a witness item, since a transaction whose stacks are all+-- empty has no witness. arbitraryWitnessTx :: Network -> Ctx -> Gen Tx arbitraryWitnessTx net ctx = arbitraryWLTx net ctx True @@ -88,7 +130,7 @@   let uniqueInps = nubBy (\a b -> a.outpoint == b.outpoint) inps   w <-     if wit-      then vectorOf (length uniqueInps) (listOf arbitraryBS)+      then (:) <$> listOf1 arbitraryBS <*> vectorOf (length uniqueInps - 1) (listOf arbitraryBS)       else return []   Tx <$> arbitrary <*> pure uniqueInps <*> pure outs <*> pure w <*> arbitrary @@ -134,10 +176,11 @@   return $ TxOut v $ marshal ctx out  -- | Arbitrary 'SigInput' with the corresponding private keys used--- to generate the 'ScriptOutput' or 'RedeemScript'.+-- to generate the 'ScriptOutput' or 'RedeemScript'. On a network without+-- a fork id, it may be a Taproot output spent by its key path. arbitrarySigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey]) arbitrarySigInput net ctx =-  oneof+  oneof $     [ wrapKey <$> arbitraryPKSigInput net ctx,       wrapKey <$> arbitraryPKHashSigInput net ctx,       arbitraryMSSigInput net ctx,@@ -145,7 +188,21 @@       wrapKey <$> arbitraryWPKHSigInput net ctx,       arbitraryWSHSigInput net ctx     ]+      <> [wrapKey <$> arbitraryTaprootSigInput net ctx | isNothing net.sigHashForkId] +-- | Arbitrary 'SigInput' of a Taproot output without a script tree, spent+-- by its key path with the key of the internal key, as a wallet of BIP 86+-- spends. The sighash is one a Taproot signature may use, other than+-- SIGHASH_SINGLE, which needs an output at the index of the input.+arbitraryTaprootSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)+arbitraryTaprootSigInput net ctx = do+  (k, p) <- arbitraryKeyPair ctx+  val <- getTestCoin <$> arbitrarySatoshi net+  op <- arbitraryOutPoint+  sh <- elements [sigHashDefault, sigHashAll, sigHashNone, setAnyoneCanPay sigHashAll, setAnyoneCanPay sigHashNone]+  let out = taprootScriptOutput ctx (TaprootOutput p.point Nothing)+  return (SigInput out val op sh Nothing, k)+ -- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPK'. arbitraryPKSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey) arbitraryPKSigInput net ctx = arbitraryAnyInput net ctx False@@ -223,22 +280,40 @@   return (SigInput out val op sh $ Just rdm, ks)  -- | Arbitrary 'Tx' (empty 'TxIn'), 'SigInputs' and private keys that can be--- passed to 'signTx' or 'detSignTx' to fully sign the 'Tx'.+-- passed to 'signTx' or 'detSignTx' to fully sign the 'Tx'. The outputs+-- spend no more than the inputs hold. arbitrarySigningData :: Network -> Ctx -> Gen (Tx, [SigInput], [PrivateKey]) arbitrarySigningData net ctx = do   v <- arbitrary   ni <- choose (1, 5)-  no <- choose (1, 5)   sigis <- vectorOf ni (arbitrarySigInput net ctx)   let uSigis = nubBy (\(a, _) (b, _) -> a.outpoint == b.outpoint) sigis+  -- The signer refuses SIGHASH_SINGLE on a legacy input without an output+  -- at its index, so every input gets an output when one asks for it+  no <- choose (if any (legacySingle net . fst) uSigis then length uSigis else 1, 5)   inps <- forM uSigis $ \(s, _) -> TxIn s.outpoint BS.empty <$> arbitrary-  outs <- vectorOf no (arbitraryTxOut net ctx)+  outs <- arbitraryOutputsWithin net ctx (sum (map ((.value) . fst) uSigis)) no   l <- arbitrary   perm <- choose (0, length inps - 1)   let tx = Tx v (permutations inps !! perm) outs [] l       keys = concatMap snd uSigis   return (tx, map fst uSigis, keys) +-- | Whether the input is to be signed with SIGHASH_SINGLE over the legacy+-- digest, which is the constant 1 when the input has no output at its+-- index. 'makeSignature' refuses to sign that.+legacySingle :: Network -> SigInput -> Bool+legacySingle net si =+  isSigHashSingle si.sighash && not (isSegwit si.script) && isNothing net.sigHashForkId++-- | Arbitrary outputs whose values add up to at most the amount given+arbitraryOutputsWithin :: Network -> Ctx -> Word64 -> Int -> Gen [TxOut]+arbitraryOutputsWithin net ctx total count =+  vectorOf count $+    TxOut+      <$> choose (0, total `div` fromIntegral count)+      <*> (marshal ctx <$> arbitraryScriptOutput net ctx)+ -- | Arbitrary transaction with empty inputs. arbitraryEmptyTx :: Network -> Ctx -> Gen Tx arbitraryEmptyTx net ctx = do@@ -251,20 +326,27 @@   s <- arbitrary   return $ Tx v (map (\op -> TxIn op BS.empty s) (nub ops)) outs [] t --- | Arbitrary partially-signed transactions.+-- | Arbitrary partially-signed transactions. The outputs spend no more+-- than the coins hold. arbitraryPartialTxs ::   Network -> Ctx -> Gen ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)]) arbitraryPartialTxs net ctx = do-  tx <- arbitraryEmptyTx net ctx+  empty <- arbitraryEmptyTx net ctx+  coins <- forM (map (.outpoint) empty.inputs) $ \op -> (,) op <$> arbitraryData+  outs <- arbitraryOutputsWithin net ctx (sum [val | (_, (_, val, _, _, _, _)) <- coins]) (length empty.outputs)+  let tx = Tx empty.version empty.inputs outs [] empty.locktime   res <--    forM (map (.outpoint) tx.inputs) $ \op -> do-      (so, val, rdmM, prvs, m, n) <- arbitraryData+    forM coins $ \(op, (so, val, rdmM, prvs, m, n)) -> do       txs <- mapM (singleSig so val rdmM tx op . (.key)) prvs       return (txs, (so, val, op, m, n))   return (concatMap fst res, map snd res)   where     singleSig so val rdmM tx op prv = do-      sh <- arbitraryValidSigHash net+      -- The signer refuses SIGHASH_SINGLE on a legacy input without an+      -- output at its index+      let index = fromMaybe 0 (findIndex ((== op) . (.outpoint)) tx.inputs)+          refused sh = index >= length tx.outputs && legacySingle net (SigInput so val op sh rdmM)+      sh <- arbitraryValidSigHash net `suchThat` (not . refused)       let sigi = SigInput so val op sh rdmM       return . fromRight (error "Could not decode transaction") $         signTx net ctx tx [sigi] [prv]@@ -294,3 +376,71 @@  wrapKey :: (SigInput, PrivateKey) -> (SigInput, [PrivateKey]) wrapKey (s, k) = (s, [k])++-- | Arbitrary PSBT of a transaction of the network, of version 0 or 2,+-- with fields of every kind: previous transactions, witness outputs,+-- sighash types, scripts, final scripts, derivation records, proprietary+-- fields, and keys the spec does not define. One of version 2 may have+-- its flags, and lock times its inputs require.+arbitraryPsbt :: Network -> Ctx -> Gen Psbt+arbitraryPsbt net ctx = do+  base <- psbtOf <$> arbitraryTx net ctx+  globals <- unknownFields+  inputs <- mapM (const inputFields) base.inputs+  outputs <- mapM (const outputFields) base.outputs+  let version0 = Psbt Version0 base.unsignedTx globals inputs outputs+  oneof [return version0, version2 version0]+  where+    -- The same PSBT in version 2, with flags at times, and lock times the+    -- inputs require: all heights, all times, or both in every input, so+    -- that one fits them all. The PSBT is rebuilt from its maps for the+    -- lock time they determine.+    version2 version0 = do+      let converted = withVersion Version2 version0+      -- The bits the spec does not define are those from the fourth on: a multiple of 8+      flagsM <- oneof [return Nothing, Just <$> (Modifiable <$> arbitrary <*> arbitrary <*> arbitrary <*> ((8 *) <$> choose (0, 31)))]+      kind <- choose (0 :: Int, 2)+      inputs <- forM converted.inputs $ \m -> do+        asks <- arbitrary+        height <- choose (1, 499999999)+        time <- choose (500000000, maxBound)+        return $+          if not asks+            then m+            else case kind of+              0 -> insertRequiredHeightLockTime height m+              1 -> insertRequiredTimeLockTime time m+              _ -> insertRequiredHeightLockTime height (insertRequiredTimeLockTime time m)+      let globals = maybe id insertTxModifiable flagsM converted.globals+      either (error . ("arbitraryPsbt: " <>)) return (psbtOfMaps globals inputs converted.outputs)+    -- Types from 0x20 on are none the spec defines for any map+    unknownFields = Map.fromList <$> resize 3 (listOf (oneof [unknownField, proprietaryField]))+    unknownField = (,) <$> (Key <$> choose (0x20, 0x7f) <*> arbitraryBS) <*> arbitraryBS+    proprietaryField = (,) <$> (proprietaryKey <$> (Proprietary <$> arbitraryBS <*> arbitrary <*> arbitraryBS)) <*> arbitraryBS+    inputFields = do+      m <- unknownFields+      fields <-+        sequence+          [ maybeField (insertNonWitnessUtxo <$> arbitraryTx net ctx),+            maybeField (insertWitnessUtxo <$> arbitraryTxOut net ctx),+            maybeField (insertSigHashType . fromIntegral <$> (arbitrary :: Gen Word32)),+            maybeField (insertInputRedeemScript <$> arbitraryScript),+            maybeField (insertInputWitnessScript <$> arbitraryScript),+            maybeField (insertFinalScriptSig <$> arbitraryBS),+            maybeField (insertFinalScriptWitness <$> resize 3 (listOf arbitraryBS)),+            derivations (insertInputDerivation ctx)+          ]+      return (foldr ($) m fields)+    outputFields = do+      m <- unknownFields+      fields <-+        sequence+          [ maybeField (insertOutputRedeemScript <$> arbitraryScript),+            maybeField (insertOutputWitnessScript <$> arbitraryScript),+            derivations (insertOutputDerivation ctx)+          ]+      return (foldr ($) m fields)+    maybeField gen = oneof [return id, gen]+    derivations insert = do+      records <- resize 2 (listOf ((,) <$> (snd <$> arbitraryKeyPair ctx) <*> (Derivation <$> arbitraryFingerprint <*> arbitraryDerivPath)))+      return $ foldr (\(key, derivation) inserted -> insert key derivation . inserted) id records
src/Haskoin/Util/Arbitrary/Util.hs view
@@ -201,7 +201,7 @@   prop ("Marshal marshalPut/marshalGet identity for " <> name) $     forAll gen $ \(s, a) -> do       (unmarshal s . marshal s) a `shouldBe` Right a-      (unmarshalLazy s . marshalLazy s) a `shouldBe` a+      (unmarshalLazy s . marshalLazy s) a `shouldBe` Right a   where     name = show $ T.typeRep $ proxy gen     proxy :: Gen (s, a) -> Proxy a
src/Haskoin/Util/Helpers.hs view
@@ -152,9 +152,12 @@  -- | Obtain 'Int' bits from beginning of 'ByteString'. Resulting 'ByteString' -- will be smallest required to hold that many bits, padded with zeroes to the--- right.+-- right. Raises an error when the 'ByteString' does not hold that many+-- bits, instead of returning fewer bits than asked. getBits :: Int -> ByteString -> ByteString getBits b bs+  | b < 0 || b > 8 * B.length bs =+      error ("getBits: " <> show b <> " bits asked of " <> show (B.length bs) <> " bytes")   | r == 0 = B.take q bs   | otherwise = i `B.snoc` l   where@@ -248,22 +251,27 @@     }  -- | Convert from one power-of-two base to another, as long as it fits in a--- 'Word'.-convertBits :: Bool -> Int -> Int -> [Word] -> ([Word], Bool)-convertBits pad frombits tobits i = (reverse yout, rem')+-- 'Word'. With padding, leftover bits fill one more output digit and the+-- conversion always succeeds. Without padding, the conversion fails when the+-- leftover bits could hold a whole input digit, or when they are not all+-- zero. That is the rule Bitcoin Core applies. It rejects strings that are+-- not the canonical encoding of their payload.+convertBits :: Bool -> Int -> Int -> [Word] -> Maybe [Word]+convertBits pad frombits tobits i+  | pad = Just . reverse $ if xbits == 0 then xout else leftover : xout+  | xbits >= frombits = Nothing+  | leftover /= 0 = Nothing+  | otherwise = Just (reverse xout)   where     (xacc, xbits, xout) = foldl' outer (0, 0, []) i-    (yout, rem')-      | pad && xbits /= 0 =-          let xout' = (xacc `shiftL` (tobits - xbits)) .&. maxv : xout-           in (xout', False)-      | pad = (xout, False)-      | xbits /= 0 = (xout, True)-      | otherwise = (xout, False)+    -- The leftover bits, moved to the top of one output digit.+    leftover = (xacc `shiftL` (tobits - xbits)) .&. maxv     maxv = 1 `shiftL` tobits - 1-    max_acc = 1 `shiftL` (frombits + tobits - 1) - 1+    -- Only this many bits of the accumulator are ever read. Masking to them+    -- keeps the accumulator from overflowing on long inputs.+    maxAcc = 1 `shiftL` (frombits + tobits - 1) - 1     outer (acc, bits, out) it =-      let acc' = ((acc `shiftL` frombits) .|. it) .&. max_acc+      let acc' = ((acc `shiftL` frombits) .|. it) .&. maxAcc           bits' = bits + frombits           (out', bits'') = inner acc' out bits'        in (acc', bits'', out')@@ -344,10 +352,17 @@   putByteString bs  getLengthBytes :: (MonadGet m) => m ByteString-getLengthBytes = do-  len <- fromIntegral <$> getWord64be-  getByteString len+getLengthBytes = getByteString =<< getCount +-- | A 64-bit count that must fit an 'Int'. A larger one fails the parse+-- instead of wrapping around to a negative number.+getCount :: (MonadGet m) => m Int+getCount = do+  n <- getWord64be+  when (n > fromIntegral (maxBound :: Int)) $+    fail ("Count too large: " <> show n)+  return (fromIntegral n)+ -- -- Fold and unfold an Integer to and from a list of its bytes --@@ -394,7 +409,7 @@  getList :: (MonadGet m) => m a -> m [a] getList f = do-  l <- fromIntegral <$> getWord64be+  l <- getCount   replicateM l f  --
src/Haskoin/Util/Marshal.hs view
@@ -28,8 +28,8 @@ unmarshal :: (Marshal s a) => s -> ByteString -> Either String a unmarshal = runGetS . marshalGet -unmarshalLazy :: (Marshal s a) => s -> Lazy.ByteString -> a-unmarshalLazy = runGetL . marshalGet+unmarshalLazy :: (Marshal s a) => s -> Lazy.ByteString -> Either String a+unmarshalLazy s = runGetS (marshalGet s) . Lazy.toStrict  class MarshalJSON s a | a -> s where   marshalValue :: s -> a -> Value
test/Haskoin/Address/Bech32Spec.hs view
@@ -19,6 +19,7 @@ import Test.HUnit import Test.Hspec +spec :: Spec spec = do   describe "bech32 checksum" $ do     it "should be valid" $@@ -28,12 +29,25 @@     it "should be case-insensitive" $       all ((== Just "test12hrzfj") . flip (bech32Encode Bech32) []) hrpCaseVariants   describe "bech32 address" $ do-    it "should be valid" $-      forM_ validChecksums (uncurry testValidChecksum)-    it "should be invalid" $-      forM_ invalidChecksums testInvalidChecksum-    it "should be case-insensitive" $-      all ((== Just "test12hrzfj") . flip (bech32Encode Bech32) []) hrpCaseVariants+    it "passes the BIP-173 and BIP-350 valid address vectors" $+      forM_ validAddresses testValidAddress+    it "rejects the BIP-173 and BIP-350 invalid address vectors" $+      forM_ invalidAddresses testInvalidAddress+    it "rejects an address with a non-ASCII look-alike character" $+      -- U+017F in place of s+      segwitDecode "tb" "tb1qrp33g0q5c5tx\x017Fp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"+        `shouldBe` Nothing+    it "rejects an address whose padding bits are not zero" $+      segwitDecode "bc" "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf308468nf"+        `shouldBe` Nothing+    it "rejects an address with an extra zero group" $+      segwitDecode "bc" "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qqnuhcm0"+        `shouldBe` Nothing+    it "rejects an address for another human-readable part" $+      segwitDecode "tb" "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" `shouldBe` Nothing+    it "decodes the version and program of a P2WPKH address" $+      segwitDecode "bc" "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"+        `shouldBe` Just (0, ByteString.unpack (fromJust (decodeHex "751e76e8199196d454941c45d1b3a323f1433bd6")))   describe "bech32 encoding/decoding" $ do     it "should not encode long data string" $       assert . isNothing $@@ -64,6 +78,29 @@           bech32Decode "10a06t8"     it "human-readable part should be case-insensitive" $       bech32Encode Bech32 "HRP" [] `shouldBe` bech32Encode Bech32 "hrp" []+    it "rejects a human-readable part with a non-ASCII character" $+      bech32Encode Bech32 "b\x00e9" [] `shouldBe` Nothing+    it "rejects a string with a non-ASCII character" $+      isNothing (bech32Decode "a12uel5l\x00e9") `shouldBe` True+    it "rejects a string longer than 90 characters" $+      isNothing (bech32Decode ("a1" <> Text.replicate 89 "q")) `shouldBe` True+  describe "base conversion" $ do+    it "toBase32 splits bytes into five-bit groups and pads with zero bits" $+      -- 11111111 becomes 11111 and 11100+      map fromWord5 (toBase32 [0xff]) `shouldBe` [31, 28 :: Int]+    it "toBase32 needs no padding for five bytes" $+      map fromWord5 (toBase32 [1, 2, 3, 4, 5]) `shouldBe` [0, 4, 1, 0, 6, 1, 0, 5 :: Int]+    it "toBase256 reverses toBase32" $+      toBase256 (map word5 [31, 28 :: Int]) `shouldBe` Just [0xff]+    it "toBase256 rejects padding bits that are not zero" $+      toBase256 (map word5 [31, 31 :: Int]) `shouldBe` Nothing+    it "toBase256 rejects padding wide enough to hold a whole group" $+      toBase256 (map word5 [31, 28, 0 :: Int]) `shouldBe` Nothing+    it "word5 keeps the low five bits" $+      fromWord5 (word5 (33 :: Int)) `shouldBe` (1 :: Int)+    it "bech32Const tells the two encodings apart" $ do+      bech32Const Bech32 `shouldBe` 1+      bech32Const Bech32m `shouldBe` 0x2bc830a3  testValidChecksum :: Bech32Encoding -> Bech32 -> Assertion testValidChecksum enc checksum = case bech32Decode checksum of
test/Haskoin/Address/CashAddrSpec.hs view
@@ -4,6 +4,8 @@ module Haskoin.Address.CashAddrSpec (spec) where  import Control.Monad+import Data.ByteString (ByteString)+import Data.ByteString qualified as B import Data.ByteString.Char8 qualified as Char8 import Data.Maybe import Data.String.Conversions@@ -127,6 +129,119 @@   describe "cashaddr larger test vectors" $     forM_ (zip [0 ..] vectors) $ \(i, vec) ->       it ("cashaddr test vector " <> show (i :: Int)) $ testCashAddr vec+  describe "cashAddrDecode" $ do+    it "decodes a lowercase address with its prefix" $+      cashAddrDecode bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+        `shouldBe` Just (0, hash20)+    it "decodes an address without prefix" $+      cashAddrDecode bch "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+        `shouldBe` Just (0, hash20)+    it "decodes an address in all uppercase" $+      cashAddrDecode bch "BITCOINCASH:QPM2QSZNHKS23Z7629MMS6S4CWEF74VCWVY22GDX6A"+        `shouldBe` Just (0, hash20)+    it "decodes a token-aware address with its type" $+      cashAddrDecode bch "bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w"+        `shouldBe` Just (2, hash20)+    it "rejects a mixed case prefix" $+      cashAddrDecode bch "BitcoinCash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+        `shouldBe` Nothing+    it "rejects mixed case data" $+      cashAddrDecode bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdX6A"+        `shouldBe` Nothing+    it "rejects the prefix of another network" $+      cashAddrDecode bch "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"+        `shouldBe` Nothing+    it "rejects an address of another network given without prefix" $+      cashAddrDecode bch "qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"+        `shouldBe` Nothing+    it "rejects a non-ASCII look-alike character" $+      -- U+212A in place of K+      cashAddrDecode bch "BITCOINCASH:QPM2QSZNH\x212AS23Z7629MMS6S4CWEF74VCWVY22GDX6A"+        `shouldBe` Nothing+    it "rejects padding bits that are not zero" $+      cashAddrDecode bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcw0c0g40rx7"+        `shouldBe` Nothing+    it "rejects an extra zero group" $+      cashAddrDecode bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvq5xhgekz9"+        `shouldBe` Nothing+    it "rejects a bad checksum" $+      cashAddrDecode bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6q"+        `shouldBe` Nothing+    it "has no result on a network without CashAddr" $+      cashAddrDecode btc "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+        `shouldBe` Nothing+  describe "cashAddrEncode" $ do+    it "encodes a P2PKH hash" $+      cashAddrEncode bch 0 hash20+        `shouldBe` Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+    it "encodes a token-aware P2SH32 hash" $+      cashAddrEncode bch 3 hash32+        `shouldBe` Just "bitcoincash:rvvxx9puznz3v6qyh5vjqv6kmgfkexzk0rx56faphrrr99syjqexy30vcqxv4"+    it "uses the prefix of the network" $ do+      cashAddrEncode bchTest 0 hash20+        `shouldBe` Just "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"+      cashAddrEncode bchRegTest 0 hash20+        `shouldBe` Just "bchreg:qpm2qsznhks23z7629mms6s4cwef74vcwv6ycwvz78"+    it "rejects a type above 15" $+      cashAddrEncode bch 16 hash20 `shouldBe` Nothing+    it "rejects a hash with a length the standard does not allow" $+      cashAddrEncode bch 0 (B.replicate 21 1) `shouldBe` Nothing+    it "has no result on a network without CashAddr" $+      cashAddrEncode btc 0 hash20 `shouldBe` Nothing+  describe "cash32 low level" $ do+    it "cash32encode writes the prefix, the data and the checksum" $ do+      cash32encode "prefix" "" `shouldBe` "prefix:x64nx6hz"+      cash32encode "bitcoincash" (B.cons 0 hash20)+        `shouldBe` "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+    it "cash32decode needs a prefix" $ do+      cash32decode "x64nx6hz" `shouldBe` Nothing+      cash32decode ":x64nx6hz" `shouldBe` Nothing+    it "cash32decode rejects mixed case" $+      cash32decode "Prefix:x64nx6hz" `shouldBe` Nothing+    it "cash32decode rejects a non-ASCII prefix" $+      cash32decode "pr\x00e9fix:x64nx6hz" `shouldBe` Nothing+    it "cash32decode rejects data shorter than a checksum" $+      cash32decode "prefix:x64nx6h" `shouldBe` Nothing+    it "cash32encodeType and cash32decodeType carry the type and the length" $ do+      cash32encodeType "pref" 1 hashF5+        `shouldBe` Just "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5"+      cash32decodeType "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5"+        `shouldBe` Just ("pref", 1, hashF5)+    it "cash32encodeType rejects a length the standard does not allow" $+      cash32encodeType "pref" 1 (B.replicate 21 1) `shouldBe` Nothing+    it "cash32decodeType rejects mixed case" $+      cash32decodeType "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtkS5" `shouldBe` Nothing+  describe "version byte" $ do+    it "decodeVersionByte splits the type and the hash size" $ do+      -- type in the high four bits, size code in the low three bits+      decodeVersionByte 0 `shouldBe` Just (0, 20)+      decodeVersionByte 11 `shouldBe` Just (1, 32)+      decodeVersionByte 16 `shouldBe` Just (2, 20)+      decodeVersionByte 127 `shouldBe` Just (15, 64)+    it "decodeVersionByte rejects a set high bit" $+      decodeVersionByte 128 `shouldBe` Nothing+    it "encodeVersionByte packs the type and the hash size" $ do+      encodeVersionByte 0 20 `shouldBe` Just 0+      encodeVersionByte 1 32 `shouldBe` Just 11+      encodeVersionByte 15 64 `shouldBe` Just 127+    it "encodeVersionByte rejects a type above 15" $+      encodeVersionByte 16 20 `shouldBe` Nothing+    it "encodeVersionByte rejects a hash size the standard does not allow" $+      encodeVersionByte 0 21 `shouldBe` Nothing++{- Fixtures -}++-- | HASH160 behind the Bitcoin Cash translation vectors above.+hash20 :: ByteString+hash20 = fromJust $ decodeHex "76a04053bda0a88bda5177b86a15c3b29f559873"++-- | SHA256 behind the BIP-173 P2WSH vector.+hash32 :: ByteString+hash32 = fromJust $ decodeHex "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"++-- | Hash from the CashAddr specification vectors.+hashF5 :: ByteString+hashF5 = fromJust $ decodeHex "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"  {- Various utilities -} 
test/Haskoin/AddressSpec.hs view
@@ -1,12 +1,20 @@ {-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}  module Haskoin.AddressSpec (spec) where +import Control.DeepSeq (force)+import Control.Exception (evaluate) import Control.Monad+import Data.Aeson (Value (String)) import Data.ByteString (ByteString) import Data.ByteString qualified as B+import Data.Bytes.Get (runGetS)+import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (deserialize, serialize) import Data.Default (def)+import Data.Either (isLeft) import Data.Maybe (fromJust, isJust) import Data.Text (Text) import Data.Text qualified as T@@ -14,8 +22,10 @@ import Haskoin.Crypto import Haskoin.Network.Constants import Haskoin.Network.Data+import Haskoin.Script import Haskoin.Util import Haskoin.Util.Arbitrary+import System.Timeout (timeout) import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck@@ -57,7 +67,460 @@       mapM_ testBase58ChkInvalidVector base58ChkInvalidVectors     it "Passes addresses witness p2sh(pwpkh) vectors" $       mapM_ (testCompatWitnessVector ctx) compatWitnessVectors+  describe "Base58" $ do+    it "decodes digits by their position in the alphabet" $+      -- 2 is digit 1 and 1 is digit 0, so 1 × 58 + 0 = 58+      decodeBase58 "21" `shouldBe` Just (B.pack [58])+    it "rejects a character outside the alphabet" $+      decodeBase58 "0" `shouldBe` Nothing+    it "rejects a non-ASCII character whose code point ends in a digit byte" $+      -- U+0131 is 0x131. Its low byte 0x31 is the digit 1.+      decodeBase58 "2\x0131" `shouldBe` Nothing+    it "rejects a checked string shorter than a checksum" $+      decodeBase58Check "1" `shouldBe` Nothing+  describe "Smart constructors" $ do+    it "witnessAddress builds a P2WPKH address from version 0 and 20 bytes" $+      witnessAddress 0 hash20Bytes `shouldBe` Just (WitnessPubKeyAddress hash20)+    it "witnessAddress builds a P2WSH address from version 0 and 32 bytes" $+      witnessAddress 0 hash32Bytes `shouldBe` Just (WitnessScriptAddress hash32)+    it "witnessAddress rejects version 0 with another program length" $+      witnessAddress 0 (B.replicate 21 1) `shouldBe` Nothing+    it "witnessAddress keeps version 1 with 32 bytes as a Taproot address" $ do+      let Just a = witnessAddress 1 hash32Bytes+      isWitnessAddress a `shouldBe` True+      addressVersion a `shouldBe` 1+      addressBytes a `shouldBe` hash32Bytes+    it "witnessAddress rejects a version above 16" $+      witnessAddress 17 hash32Bytes `shouldBe` Nothing+    it "witnessAddress rejects a program shorter than 2 bytes" $+      witnessAddress 1 (B.pack [1]) `shouldBe` Nothing+    it "witnessAddress rejects a program longer than 40 bytes" $+      witnessAddress 1 (B.replicate 41 1) `shouldBe` Nothing+    it "cashAddress builds a P2PKH address from type 0 and 20 bytes" $+      cashAddress 0 hash20Bytes `shouldBe` Just (PubKeyAddress hash20)+    it "cashAddress builds a P2SH address from type 1 and 20 bytes" $+      cashAddress 1 hash20Bytes `shouldBe` Just (ScriptAddress hash20)+    it "cashAddress builds a P2SH32 address from type 1 and 32 bytes" $+      cashAddress 1 hash32Bytes `shouldBe` Just (Script32Address hash32)+    it "cashAddress keeps a token-aware address as a generic CashAddr" $ do+      let Just a = cashAddress 2 hash20Bytes+      isCashAddress a `shouldBe` True+      addressVersion a `shouldBe` 2+      addressBytes a `shouldBe` hash20Bytes+    it "cashAddress rejects a type above 15" $+      cashAddress 16 hash20Bytes `shouldBe` Nothing+    it "cashAddress rejects a hash length the standard does not allow" $+      cashAddress 0 (B.replicate 21 1) `shouldBe` Nothing+  describe "Address predicates" $+    it "each predicate is true only for its own kind of address" $+      forM_ predicateTable $ \(name, a, kind) ->+        assertEqual name [k == kind | k <- [0 .. 6]] (map ($ a) predicates)+  describe "Address accessors" $ do+    it "addressHash160 gives the hash of every address paying to a 160-bit hash" $ do+      addressHash160 p2pkh `shouldBe` Just hash20+      addressHash160 p2sh `shouldBe` Just hash20+      addressHash160 p2wpkh `shouldBe` Just hash20+      addressHash160 tokenP2pkh `shouldBe` Just hash20+      addressHash160 tokenP2sh `shouldBe` Just hash20+    it "addressHash160 gives nothing for other kinds" $ do+      addressHash160 p2wsh `shouldBe` Nothing+      addressHash160 p2tr `shouldBe` Nothing+      addressHash160 cash15 `shouldBe` Nothing+    it "addressHash256 gives the hash of every address paying to a 256-bit hash" $ do+      addressHash256 p2wsh `shouldBe` Just hash32+      addressHash256 p2sh32 `shouldBe` Just hash32+      addressHash256 tokenP2sh32 `shouldBe` Just hash32+    it "addressHash256 gives nothing for other kinds" $ do+      addressHash256 p2pkh `shouldBe` Nothing+      addressHash256 p2tr `shouldBe` Nothing+    it "addressVersion is the witness version of SegWit kinds" $ do+      addressVersion p2wpkh `shouldBe` 0+      addressVersion p2wsh `shouldBe` 0+      addressVersion p2tr `shouldBe` 1+      addressVersion witness16 `shouldBe` 16+    it "addressVersion is the CashAddr type of other kinds" $ do+      addressVersion p2pkh `shouldBe` 0+      addressVersion p2sh `shouldBe` 1+      addressVersion p2sh32 `shouldBe` 1+      addressVersion tokenP2pkh `shouldBe` 2+      addressVersion tokenP2sh `shouldBe` 3+      addressVersion cash15 `shouldBe` 15+    it "addressBytes is the raw hash or program without any prefix" $ do+      addressBytes p2pkh `shouldBe` hash20Bytes+      addressBytes p2sh `shouldBe` hash20Bytes+      addressBytes p2wpkh `shouldBe` hash20Bytes+      addressBytes p2wsh `shouldBe` hash32Bytes+      addressBytes p2sh32 `shouldBe` hash32Bytes+      addressBytes p2tr `shouldBe` hash32Bytes+      addressBytes witness16 `shouldBe` B.pack [1 .. 40]+      addressBytes tokenP2pkh `shouldBe` hash20Bytes+      addressBytes cash15 `shouldBe` B.pack [1 .. 24]+  describe "addrToText" $ do+    it "encodes every address kind on every network" $+      forM_ textTable $ \(net, a, txt) ->+        assertEqual (net.name <> " " <> T.unpack txt) (Just txt) (addrToText net a)+    it "uses the testnet prefixes on every test and regression network" $ do+      addrToText btcTest4 p2pkh `shouldBe` Just "mrLC19Je2BuWQDkWSTriGYPyQJXKkkBmCx"+      addrToText btcRegTest p2sh `shouldBe` Just "2N44ThNe8NXHyv4bsX8AoVCXquBRW94Ls7W"+      addrToText btcTest4 p2wpkh `shouldBe` Just "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"+      addrToText btcRegTest p2wpkh `shouldBe` Just "bcrt1qw6syq5aa5z5ghkj3w7ux59wrk204txrndehahp"+      addrToText bchTest4 p2pkh `shouldBe` Just "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"+      addrToText bchRegTest p2pkh `shouldBe` Just "bchreg:qpm2qsznhks23z7629mms6s4cwef74vcwv6ycwvz78"+    it "has no text for a SegWit address on a Bitcoin Cash network" $ do+      addrToText bch p2wpkh `shouldBe` Nothing+      addrToText bch p2wsh `shouldBe` Nothing+      addrToText bch p2tr `shouldBe` Nothing+    it "has no text for P2SH32 or a CashAddr kind on a Bitcoin network" $ do+      addrToText btc p2sh32 `shouldBe` Nothing+      addrToText btc tokenP2pkh `shouldBe` Nothing+      addrToText btc cash15 `shouldBe` Nothing+  describe "addrToBase58, addrToBech32 and addrToCash" $ do+    it "addrToBase58 gives the legacy form of a Bitcoin Cash address" $ do+      addrToBase58 bch p2pkh `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"+      addrToBase58 bch p2sh `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"+      addrToBase58 bchTest p2sh `shouldBe` Just "2N44ThNe8NXHyv4bsX8AoVCXquBRW94Ls7W"+    it "addrToBase58 has no form for other kinds" $ do+      addrToBase58 btc p2wpkh `shouldBe` Nothing+      addrToBase58 bch p2sh32 `shouldBe` Nothing+      addrToBase58 bch tokenP2pkh `shouldBe` Nothing+    it "addrToBech32 encodes SegWit kinds on SegWit networks only" $ do+      addrToBech32 btc p2wpkh `shouldBe` Just "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"+      addrToBech32 btc p2pkh `shouldBe` Nothing+      addrToBech32 bch p2wpkh `shouldBe` Nothing+    it "addrToCash encodes on Bitcoin Cash networks only" $ do+      addrToCash bch p2pkh `shouldBe` Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+      addrToCash btc p2pkh `shouldBe` Nothing+      addrToCash bch p2wpkh `shouldBe` Nothing+  describe "textToAddr" $ do+    it "decodes every form that addrToText encodes" $+      forM_ textTable $ \(net, a, txt) ->+        assertEqual (net.name <> " " <> T.unpack txt) (Just a) (textToAddr net txt)+    it "accepts a CashAddr without prefix" $+      textToAddr bch "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" `shouldBe` Just p2pkh+    it "accepts a CashAddr in all uppercase" $+      textToAddr bch "BITCOINCASH:QPM2QSZNHKS23Z7629MMS6S4CWEF74VCWVY22GDX6A" `shouldBe` Just p2pkh+    it "accepts a Bech32 address in all uppercase" $+      textToAddr btc "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"+        `shouldBe` Just (WitnessPubKeyAddress "751e76e8199196d454941c45d1b3a323f1433bd6")+    it "accepts a legacy Base58 address on a Bitcoin Cash network" $+      textToAddr bch "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" `shouldBe` Just p2pkh+    it "rejects a CashAddr with a mixed case prefix" $+      textToAddr bch "BitcoinCash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" `shouldBe` Nothing+    it "rejects a CashAddr with mixed case data" $+      textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdX6A" `shouldBe` Nothing+    it "rejects a Bech32 address with mixed case" $+      textToAddr btcTest "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7" `shouldBe` Nothing+    it "rejects a CashAddr whose padding bits are not zero" $+      textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcw0c0g40rx7" `shouldBe` Nothing+    it "rejects a CashAddr with an extra zero group" $+      textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvq5xhgekz9" `shouldBe` Nothing+    it "rejects a Bech32 address whose padding bits are not zero" $+      textToAddr btc "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf308468nf" `shouldBe` Nothing+    it "rejects a Bech32 address with an extra zero group" $+      textToAddr btc "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qqnuhcm0" `shouldBe` Nothing+    it "rejects a Base58 address with a non-ASCII look-alike character" $+      -- U+0141 in place of A+      textToAddr btc "1BpEi6DfD\x0141UFd7GtittLSdBeYJvcoaVggu" `shouldBe` Nothing+    it "rejects a Bech32 address with a non-ASCII look-alike character" $+      -- U+017F in place of s+      textToAddr btcTest "tb1qrp33g0q5c5tx\x017Fp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7" `shouldBe` Nothing+    it "rejects a CashAddr with a non-ASCII look-alike character" $+      -- U+212A in place of K+      textToAddr bch "BITCOINCASH:QPM2QSZNH\x212AS23Z7629MMS6S4CWEF74VCWVY22GDX6A" `shouldBe` Nothing+    it "rejects an address of another network" $+      forM_ otherNetworkTable $ \(net, txt) ->+        assertEqual (net.name <> " " <> T.unpack txt) Nothing (textToAddr net txt)+    it "rejects a Base58 address with a bad checksum" $+      textToAddr btc "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggv" `shouldBe` Nothing+    it "rejects a Base58 payload that is not 21 bytes" $ do+      -- version byte, the hash and one extra byte+      textToAddr btc "1pjVWN2segoHYNh9Hv1ap6hzhC7eqEpz8oy" `shouldBe` Nothing+      -- version byte and 19 bytes of hash+      textToAddr btc "13T7H7AfZfPwWvz51tBGjWVhDz5TJEMr9" `shouldBe` Nothing+    it "rejects a Base58 version byte that is neither P2PKH nor P2SH" $+      -- version byte 1 and the hash+      textToAddr btc "b9qhCWwvLw8SYQykKDevkTSApBZZqXzeV" `shouldBe` Nothing+    it "rejects surrounding whitespace" $+      textToAddr btc " 1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\n" `shouldBe` Nothing+    it "rejects a long string of Base58 characters without decoding it" $+      timeout 2000000 (evaluate (textToAddr btc (T.replicate 500000 "z")))+        `shouldReturn` Just Nothing+  describe "Addresses from public keys" $ do+    it "pubKeyAddr hashes a compressed key" $+      addrToText btc (pubKeyAddr ctx (compressedKey ctx))+        `shouldBe` Just "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"+    it "pubKeyAddr hashes an uncompressed key in its uncompressed form" $+      addrToText btc (pubKeyAddr ctx (uncompressedKey ctx))+        `shouldBe` Just "1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm"+    it "pubKeyWitnessAddr gives the P2WPKH address of a compressed key" $+      (addrToText btc =<< pubKeyWitnessAddr ctx (compressedKey ctx))+        `shouldBe` Just "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"+    it "pubKeyWitnessAddr refuses an uncompressed key" $+      pubKeyWitnessAddr ctx (uncompressedKey ctx) `shouldBe` Nothing+    it "pubKeyCompatWitnessAddr gives the P2SH-P2WPKH address of a compressed key" $+      (addrToText btc =<< pubKeyCompatWitnessAddr ctx (compressedKey ctx))+        `shouldBe` Just "3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN"+    it "pubKeyCompatWitnessAddr refuses an uncompressed key" $+      pubKeyCompatWitnessAddr ctx (uncompressedKey ctx) `shouldBe` Nothing+    it "p2pkhAddr, p2shAddr, p2wpkhAddr and p2wshAddr wrap a hash" $ do+      addrToText btc (p2pkhAddr hash20) `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"+      addrToText btc (p2shAddr hash20) `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"+      addrToText btc (p2wpkhAddr hash20) `shouldBe` Just "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"+      addrToText btc (p2wshAddr hash32) `shouldBe` Just "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"+  describe "Addresses from scripts" $ do+    it "payToScriptAddress hashes the output script with HASH160" $+      addrToText btc (payToScriptAddress ctx (multisig ctx))+        `shouldBe` Just "38fEX6RbBBMmpu3nbbuULku1xyrrzqqqnE"+    it "payToWitnessScriptAddress hashes the output script with SHA256" $+      addrToText btc (payToWitnessScriptAddress ctx (multisig ctx))+        `shouldBe` Just "bc1qd6e6c86xp56gw8pty8suuqhscpttea2c5m2fggzjkxzk5nl9faks5pehxu"+    it "payToNestedScriptAddress wraps the P2WSH script in P2SH" $+      addrToText btc (payToNestedScriptAddress ctx (multisig ctx))+        `shouldBe` Just "3JSuZ22mPR2aD5bf77KsGi36V5ar3p87LX"+  describe "addressToOutput, addressToScript and addressToScriptBS" $ do+    it "builds the locking script of every address kind" $+      forM_ scriptTable $ \(a, hex) ->+        assertEqual (T.unpack hex) (Just hex) (encodeHex <$> addressToScriptBS ctx a)+    it "pays a token-aware P2PKH address with the plain P2PKH script" $+      (encodeHex <$> addressToScriptBS ctx tokenP2pkh)+        `shouldBe` Just "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"+    it "pays a token-aware P2SH address with the plain P2SH script" $+      (encodeHex <$> addressToScriptBS ctx tokenP2sh)+        `shouldBe` Just "a91476a04053bda0a88bda5177b86a15c3b29f55987387"+    it "pays a token-aware P2SH32 address with the plain P2SH32 script" $+      (encodeHex <$> addressToScriptBS ctx tokenP2sh32)+        `shouldBe` Just "aa201863143c14c5166804bd19203356da136c985678cd4d27a1b8c632960490326287"+    it "has no script for a CashAddr type with no known script" $ do+      addressToOutput cash15 `shouldBe` Nothing+      addressToOutput (fromJust (cashAddress 0 hash32Bytes)) `shouldBe` Nothing+      addressToOutput (fromJust (cashAddress 2 hash32Bytes)) `shouldBe` Nothing+    it "addressToOutput gives the matching ScriptOutput" $ do+      addressToOutput p2pkh `shouldBe` Just (PayPKHash hash20)+      addressToOutput p2wsh `shouldBe` Just (PayWitnessScriptHash hash32)+      addressToOutput p2tr `shouldBe` Just (PayWitness 1 hash32Bytes)+      addressToOutput tokenP2sh32 `shouldBe` Just (PayScript32Hash hash32)+    it "addressToScript gives the script AST" $+      ((.ops) <$> addressToScript ctx p2sh)+        `shouldBe` Just [OP_HASH160, OP_PUSHDATA hash20Bytes OPCODE, OP_EQUAL]+  describe "outputAddress, scriptToAddress and scriptToAddressBS" $ do+    it "recovers the address of every standard locking script" $+      forM_ scriptTable $ \(a, hex) ->+        assertEqual (T.unpack hex) (Right a) (scriptToAddressBS ctx (fromJust (decodeHex hex)))+    it "scriptToAddress works on the script AST" $+      (scriptToAddress ctx =<< runGetS deserialize (fromJust (decodeHex "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac")))+        `shouldBe` Right p2pkh+    it "has no address for a pay-to-public-key script" $+      scriptToAddressBS ctx (fromJust (decodeHex p2pkScript))+        `shouldBe` Left "Could not decode address"+    it "has no address for a bare multisig script" $+      scriptToAddressBS ctx (fromJust (decodeHex multisigScript))+        `shouldBe` Left "Could not decode address"+    it "has no address for a data carrier script" $+      scriptToAddressBS ctx (fromJust (decodeHex "6a04deadbeef"))+        `shouldBe` Left "Could not decode address"+    it "fails on a script that is not standard" $+      scriptToAddressBS ctx (fromJust (decodeHex "51")) `shouldSatisfy` isLeft+    it "outputAddress has no address for a pay-to-public-key output" $+      outputAddress ctx (PayPK (compressedKey ctx)) `shouldBe` Nothing+  describe "inputAddress" $ do+    it "gives the P2PKH address of the key in a public key hash input" $+      (addrToText btc =<< inputAddress ctx (RegularInput (SpendPKHash TxSignatureEmpty (compressedKey ctx))))+        `shouldBe` Just "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"+    it "gives the P2SH address of the redeem script in a script hash input" $+      (addrToText btc =<< inputAddress ctx (ScriptHashInput (SpendMulSig [TxSignatureEmpty]) (multisig ctx)))+        `shouldBe` Just "38fEX6RbBBMmpu3nbbuULku1xyrrzqqqnE"+    it "gives the P2SH address of the wrapped program in a nested witness input" $+      -- The P2SH-P2WPKH example of BIP 143: redeem script 0014 79091972…,+      -- script hash 4733f37cf4db86fbc2efed2500b4f4e49f312023.+      (addrToText btc =<< inputAddress ctx (NestedWitnessInput (PayWitnessPKHash "79091972186c449eb1ded22b78e40d009bdf0089")))+        `shouldBe` Just "38BW8nqpHSWpkf5sXrQd2xYwvnPJwP59ic"+    it "has no address for a pay-to-public-key input" $+      inputAddress ctx (RegularInput (SpendPK TxSignatureEmpty)) `shouldBe` Nothing+    it "has no address for a bare multisig input" $+      inputAddress ctx (RegularInput (SpendMulSig [TxSignatureEmpty])) `shouldBe` Nothing+  describe "Binary serialization" $ do+    it "writes a P2PKH address as a type byte and the hash" $+      encodeHex (runPutS (serialize p2pkh))+        `shouldBe` "0076a04053bda0a88bda5177b86a15c3b29f559873"+    it "writes a Taproot address as a type byte, the version and the sized program" $+      encodeHex (runPutS (serialize p2tr))+        `shouldBe` "0401" <> "0000000000000020" <> "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"+    it "writes a token-aware address as a type byte, the CashAddr type and the sized hash" $+      encodeHex (runPutS (serialize tokenP2pkh))+        `shouldBe` "0602" <> "0000000000000014" <> "76a04053bda0a88bda5177b86a15c3b29f559873"+    it "rejects an unknown type byte" $+      deserializeAddress "0776a04053bda0a88bda5177b86a15c3b29f559873" `shouldSatisfy` isLeft+    it "rejects a witness version above 16" $+      deserializeAddress ("0411" <> "0000000000000002" <> "0102") `shouldSatisfy` isLeft+    it "rejects a witness program longer than 40 bytes" $+      deserializeAddress ("0401" <> "0000000000000029" <> T.replicate 41 "01") `shouldSatisfy` isLeft+    it "rejects a sized payload whose length does not fit in memory" $+      deserializeAddress ("0401" <> "ffffffffffffffff" <> "0102") `shouldSatisfy` isLeft+    it "reads a version 0 witness program as the P2WPKH constructor" $+      deserializeAddress ("0400" <> "0000000000000014" <> "76a04053bda0a88bda5177b86a15c3b29f559873")+        `shouldBe` Right (WitnessPubKeyAddress hash20)+    it "reads a CashAddr of type 0 with 20 bytes as the P2PKH constructor" $+      deserializeAddress ("0600" <> "0000000000000014" <> "76a04053bda0a88bda5177b86a15c3b29f559873")+        `shouldBe` Right (PubKeyAddress hash20)+    it "rejects a CashAddr with a hash size the standard does not allow" $+      deserializeAddress ("0602" <> "0000000000000015" <> T.replicate 21 "01") `shouldSatisfy` isLeft+  describe "JSON" $ do+    it "encodes an address as its text form" $ do+      marshalValue btc p2pkh `shouldBe` String "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"+      marshalJSON btc p2pkh `shouldBe` "\"1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\""+      marshalJSON bch tokenP2pkh `shouldBe` "\"bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w\""+    it "decodes the text form" $+      unmarshalJSON btc "\"1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\"" `shouldBe` Just p2pkh+    it "rejects text that is not an address" $ do+      unmarshalJSON btc "\"hello\"" `shouldBe` (Nothing :: Maybe Address)+      unmarshalJSON btc "null" `shouldBe` (Nothing :: Maybe Address)+    it "fails loudly instead of writing null when the address has no text form" $ do+      evaluate (force (marshalValue bch p2wpkh)) `shouldThrow` anyErrorCall+      evaluate (force (marshalJSON btc p2sh32)) `shouldThrow` anyErrorCall +{- Fixtures -}++-- | HASH160 behind the CashAddr translation vectors.+hash20 :: Hash160+hash20 = "76a04053bda0a88bda5177b86a15c3b29f559873"++hash20Bytes :: ByteString+hash20Bytes = fromJust $ decodeHex "76a04053bda0a88bda5177b86a15c3b29f559873"++-- | SHA256 behind the BIP-173 P2WSH vector.+hash32 :: Hash256+hash32 = "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"++hash32Bytes :: ByteString+hash32Bytes = fromJust $ decodeHex "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"++p2pkh, p2sh, p2wpkh, p2wsh, p2sh32, p2tr, witness16 :: Address+p2pkh = pubKeyAddress hash20+p2sh = scriptAddress hash20+p2wpkh = witnessPubKeyAddress hash20+p2wsh = witnessScriptAddress hash32+p2sh32 = script32Address hash32+p2tr = fromJust $ witnessAddress 1 hash32Bytes+witness16 = fromJust $ witnessAddress 16 (B.pack [1 .. 40])++tokenP2pkh, tokenP2sh, tokenP2sh32, cash15 :: Address+tokenP2pkh = fromJust $ cashAddress 2 hash20Bytes+tokenP2sh = fromJust $ cashAddress 3 hash20Bytes+tokenP2sh32 = fromJust $ cashAddress 3 hash32Bytes+cash15 = fromJust $ cashAddress 15 (B.pack [1 .. 24])++predicates :: [Address -> Bool]+predicates =+  [ isPubKeyAddress,+    isScriptAddress,+    isWitnessPubKeyAddress,+    isWitnessScriptAddress,+    isWitnessAddress,+    isScript32Address,+    isCashAddress+  ]++-- | Each address with the index of the only predicate that holds for it.+predicateTable :: [(String, Address, Int)]+predicateTable =+  [ ("P2PKH", p2pkh, 0),+    ("P2SH", p2sh, 1),+    ("P2WPKH", p2wpkh, 2),+    ("P2WSH", p2wsh, 3),+    ("P2TR", p2tr, 4),+    ("P2SH32", p2sh32, 5),+    ("token-aware P2PKH", tokenP2pkh, 6)+  ]++-- | Text form of every address kind on the main networks.+textTable :: [(Network, Address, Text)]+textTable =+  [ (btc, p2pkh, "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"),+    (btc, p2sh, "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"),+    (btc, p2wpkh, "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"),+    (btc, p2wsh, "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"),+    (btc, p2tr, "bc1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qj0fj5d"),+    (btc, witness16, "bc1sqypqxpq9qcrsszg2pvxq6rs0zqg3yyc5z5tpwxqergd3c8g7ruszzg3rysjjvfeg7dt0md"),+    (btcTest, p2pkh, "mrLC19Je2BuWQDkWSTriGYPyQJXKkkBmCx"),+    (btcTest, p2sh, "2N44ThNe8NXHyv4bsX8AoVCXquBRW94Ls7W"),+    (btcTest, p2wpkh, "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"),+    (btcTest, p2wsh, "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"),+    (btcTest, p2tr, "tb1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q98lawz"),+    (bch, p2pkh, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"),+    (bch, p2sh, "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"),+    (bch, p2sh32, "bitcoincash:pvvxx9puznz3v6qyh5vjqv6kmgfkexzk0rx56faphrrr99syjqexyrusee847"),+    (bch, tokenP2pkh, "bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w"),+    (bch, tokenP2sh, "bitcoincash:rpm2qsznhks23z7629mms6s4cwef74vcwv59yeyr7n"),+    (bch, tokenP2sh32, "bitcoincash:rvvxx9puznz3v6qyh5vjqv6kmgfkexzk0rx56faphrrr99syjqexy30vcqxv4"),+    (bch, cash15, "bitcoincash:0yqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9cc7akt68km"),+    (bchTest, p2pkh, "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"),+    (bchTest, p2sh, "bchtest:ppm2qsznhks23z7629mms6s4cwef74vcwvhanqgjxu")+  ]++-- | Valid addresses of one network that another network must reject.+otherNetworkTable :: [(Network, Text)]+otherNetworkTable =+  [ (btc, "mrLC19Je2BuWQDkWSTriGYPyQJXKkkBmCx"),+    (btc, "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"),+    (btc, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"),+    (btc, "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"),+    (btcTest, "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"),+    (btcTest, "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"),+    (btcRegTest, "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"),+    (bch, "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"),+    (bch, "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"),+    (bch, "qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"),+    (bchTest, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")+  ]++-- | Locking script of every address kind that has one.+scriptTable :: [(Address, Text)]+scriptTable =+  [ (p2pkh, "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"),+    (p2sh, "a91476a04053bda0a88bda5177b86a15c3b29f55987387"),+    (p2sh32, "aa201863143c14c5166804bd19203356da136c985678cd4d27a1b8c632960490326287"),+    (p2wpkh, "001476a04053bda0a88bda5177b86a15c3b29f559873"),+    (p2wsh, "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),+    (p2tr, "51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),+    (witness16, "6028" <> "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728")+  ]++-- | Public key of private key 1, in both forms.+compressedKey, uncompressedKey :: Ctx -> PublicKey+compressedKey ctx =+  derivePublicKey ctx . fromJust $+    fromWif btc "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"+uncompressedKey ctx =+  derivePublicKey ctx . fromJust $+    fromWif btc "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf"++-- | Public key of private key 2.+secondKey :: Ctx -> PublicKey+secondKey ctx =+  either error id . unmarshal ctx . fromJust $+    decodeHex "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"++-- | 1-of-2 multisig of the keys of private keys 1 and 2.+multisig :: Ctx -> ScriptOutput+multisig ctx = PayMulSig [compressedKey ctx, secondKey ctx] 1++multisigScript :: Text+multisigScript =+  "5121"+    <> "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"+    <> "21"+    <> "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"+    <> "52ae"++-- | Pay to the public key of private key 1.+p2pkScript :: Text+p2pkScript = "210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac"++deserializeAddress :: Text -> Either String Address+deserializeAddress = runGetS deserialize . fromJust . decodeHex++{- Vectors -}+ testVector :: (ByteString, Text, Text) -> Assertion testVector (bs, e, chk) = do   assertEqual "encodeBase58" e b58@@ -186,7 +649,7 @@   let seckeyM = fromWif net seckey   assertBool "decode seckey" (isJust seckeyM)   let pubkey = derivePublicKey ctx (fromJust seckeyM)-  let addrM = addrToText btcTest (pubKeyCompatWitnessAddr ctx pubkey)+  let addrM = addrToText btcTest =<< pubKeyCompatWitnessAddr ctx pubkey   assertBool "address can be encoded" (isJust addrM)   assertEqual "witness address matches" addr (fromJust addrM) 
test/Haskoin/BlockSpec.hs view
@@ -1,504 +1,1358 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}--module Haskoin.BlockSpec-  ( spec,-  )-where--import Control.Monad-import Control.Monad.State.Strict-import Data.Default (def)-import Data.Either (fromRight)-import Data.Maybe (fromJust)-import Data.String (fromString)-import Data.String.Conversions (cs)-import Data.Text (Text)-import Data.Word (Word32)-import Haskoin.Block-import Haskoin.Crypto-import Haskoin.Network.Constants-import Haskoin.Network.Data-import Haskoin.Transaction-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Test.HUnit hiding (State)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck-import Text.Printf (printf)--identityTests :: Ctx -> IdentityTests-identityTests ctx =-  def-    { readTests =-        [ ReadBox (flip arbitraryBlock ctx =<< arbitraryNetwork),-          ReadBox arbitraryBlockHash,-          ReadBox arbitraryBlockHeader,-          ReadBox arbitraryGetBlocks,-          ReadBox arbitraryGetHeaders,-          ReadBox arbitraryHeaders,-          ReadBox arbitraryMerkleBlock,-          ReadBox arbitraryBlockNode,-          ReadBox arbitraryHeaderMemory-        ],-      jsonTests =-        [ JsonBox (flip arbitraryBlock ctx =<< arbitraryNetwork),-          JsonBox arbitraryBlockHash,-          JsonBox arbitraryBlockHeader-        ],-      serialTests =-        [ SerialBox (flip arbitraryBlock ctx =<< arbitraryNetwork),-          SerialBox arbitraryBlockHash,-          SerialBox arbitraryBlockHeader,-          SerialBox arbitraryGetBlocks,-          SerialBox arbitraryGetHeaders,-          SerialBox arbitraryHeaders,-          SerialBox arbitraryMerkleBlock,-          SerialBox arbitraryBlockNode-        ]-    }--myTime :: Timestamp-myTime = 1499083075--withChain :: Network -> State HeaderMemory a -> a-withChain net f = evalState f (initialChain net)--chain :: (BlockHeaders m) => Network -> BlockHeader -> Int -> m ()-chain net bh i = do-  bnsE <- connectBlocks net myTime bhs-  either error (const $ return ()) bnsE-  where-    bhs = appendBlocks net 6 bh i---- | Regtest with the BIP94 rules and a retarget every ten blocks.-bip94Net :: Network-bip94Net =-  (btcRegTest :: Network)-    { bip94 = True,-      powNoRetarget = False,-      targetTimespan = 10 * 600-    }---- | The same network without the BIP94 rules.-noBip94Net :: Network-noBip94Net = (bip94Net :: Network) {bip94 = False}---- | Mine a header on top of the parent with the given timestamp and bits.-mineOn :: Network -> BlockHeader -> Timestamp -> Word32 -> BlockHeader-mineOn net parent time bits =-  mineBlock net 0 (parent :: BlockHeader) {prev = headerHash parent, timestamp = time, bits = bits}---- | Mine a chain of headers with the given timestamps and bits.-mineChain :: Network -> BlockHeader -> [(Timestamp, Word32)] -> [BlockHeader]-mineChain net parent = drop 1 . scanl (\p (time, bits) -> mineOn net p time bits) parent--spec :: Spec-spec = prepareContext $ \ctx -> do-  testIdentity $ identityTests ctx-  describe "blockchain headers" $ do-    it "gets best block on bchRegTest" $-      let net = bchRegTest-          bb =-            withChain net $ do-              chain net net.genesisHeader 100-              getBestBlockHeader-       in bb.height `shouldBe` 100-    it "builds a block locator on bchRegTest" $-      let net = bchRegTest-          loc =-            withChain net $ do-              chain net net.genesisHeader 100-              bb <- getBestBlockHeader-              blockLocatorNodes bb-          heights = map (.height) loc-       in heights `shouldBe` [100, 99 .. 90] <> [88, 84, 76, 60, 28, 0]-    it "follows split chains on bchRegTest" $-      let net = bchRegTest-          bb = withChain net $ splitChain net >> getBestBlockHeader-       in bb.height `shouldBe` 4035-  describe "testnet4" $ do-    it "has the BIP94 genesis block" $-      headerHash btcTest4.genesisHeader-        `shouldBe` "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043"-    it "has the Bitcoin Cash testnet4 genesis block" $-      headerHash bchTest4.genesisHeader-        `shouldBe` "000000001dd410c49a788668ce26751718cc797474d3152a5fc073dd44fd9f7b"-    it "rejects the first block of a retarget period when it is too early" $ do-      -- Blocks 1 to 8 are ten minutes apart; block 9 jumps two hours ahead.-      let t = bip94Net.genesisHeader.timestamp-          hs =-            mineChain bip94Net bip94Net.genesisHeader $-              [(t + 600 * i, 0x207fffff) | i <- [1 .. 8]] ++ [(t + 12000, 0x207fffff)]-          par = last hs-          b10 = mineOn bip94Net par (t + 11400) 0x207fffff-          connect net chain' h = withChain net $ do-            connectBlocks net myTime chain' >>= either error (const (return ()))-            fmap (.height) <$> connectBlock net myTime h-      -- Block 10 starts a retarget period, so it may be at most 600 seconds-      -- earlier than block 9.-      connect bip94Net hs (mineOn bip94Net par (t + 11399) 0x207fffff)-        `shouldBe` Left "Block timestamp too early for retarget: 1296700001"-      connect bip94Net hs b10 `shouldBe` Right 10-      -- Block 11 does not start a period.-      connect bip94Net (hs ++ [b10]) (mineOn bip94Net b10 (t + 10799) 0x207fffff)-        `shouldBe` Right 11-      connect noBip94Net hs (mineOn noBip94Net par (t + 11399) 0x207fffff)-        `shouldBe` Right 10-    it "bases the retarget on the first block of the period" $ do-      -- Blocks 10 to 19 form a retarget period that takes four times the-      -- target timespan. Its last block uses the min-difficulty exception.-      let t = bip94Net.genesisHeader.timestamp-          hs =-            mineChain bip94Net bip94Net.genesisHeader $-              [(t + 600 * i, 0x207fffff) | i <- [1 .. 9]]-                ++ [(t + 6000 + 600 * i, 0x1f7fffff) | i <- [0 .. 8]]-                ++ [(t + 30000, 0x207fffff)]-          next net = withChain net $ do-            connectBlocks net myTime hs >>= either error (const (return ()))-            par <- getBestBlockHeader-            let bh = (par.header :: BlockHeader) {prev = headerHash par.header, timestamp = t + 30600}-            nextWorkRequired net par bh-      -- Four times the difficulty of block 10.-      next bip94Net `shouldBe` 0x2001ffff-      -- Without BIP94, block 19 drags the next period down to min difficulty.-      next noBip94Net `shouldBe` 0x207fffff-  describe "emergency difficulty adjustment" $-    it "retargets at the end of a period" $ do-      -- Blocks 10 to 19 form a retarget period that takes four times the-      -- target timespan.-      let net = (noBip94Net :: Network) {edaHeight = Just 1}-          t = net.genesisHeader.timestamp-          hs =-            mineChain net net.genesisHeader $-              [(t + 600 * i, 0x207fffff) | i <- [1 .. 9]]-                ++ [(t + 6000 + 600 * i, 0x1f7fffff) | i <- [0 .. 8]]-                ++ [(t + 30000, 0x1f7fffff)]-          next = withChain net $ do-            connectBlocks net myTime hs >>= either error (const (return ()))-            par <- getBestBlockHeader-            let bh = (par.header :: BlockHeader) {prev = headerHash par.header, timestamp = t + 30600}-            nextWorkRequired net par bh-      next `shouldBe` 0x2001ffff-  describe "block hash" $ do-    prop "encodes and decodes block hash" $-      forAll arbitraryBlockHash $ \h ->-        hexToBlockHash (blockHashToHex h) == Just h-    prop "from string block hash" $-      forAll arbitraryBlockHash $ \h ->-        fromString (cs $ blockHashToHex h) == h-  describe "merkle trees" $ do-    prop "builds tree of right width at height 1" testTreeWidth-    prop "builds tree of right width at height 0" testBaseWidth-    prop "builds and extracts partial merkle tree" $-      forAll arbitraryNetwork $ \net ->-        forAll-          (listOf1 ((,) <$> arbitraryTxHash <*> arbitrary))-          (buildExtractTree net)-    it "merkle root test vectors" $ mapM_ runMerkleVector merkleVectors-  describe "compact number" $ do-    it "compact number local vectors" testCompact-    it "compact number imported vectors" testCompactBitcoinCore-  describe "asert" $-    mapM_-      ( \x ->-          asertTests $-            "test_vectors_aserti3-2d_run" ++ printf "%02d" x ++ ".txt"-      )-      [(1 :: Int) .. 12]-  describe "helper functions" $ do-    it "computes bitcoin block subsidy correctly" (testSubsidy btc)-    it "computes regtest block subsidy correctly" (testSubsidy btcRegTest)---- 0 → → 2015 → → → → → → → 4031---       ↓---       → → 2035 → → → → → → 4035*---           ↓---           → → 2185-splitChain :: Network -> State HeaderMemory ()-splitChain net = do-  start <- go 1 net.genesisHeader 2015-  e 2015 (head start)-  tail1 <- go 2 (head start).header 2016-  e 4031 (head tail1)-  tail2 <- go 3 (head start).header 20-  e 2035 (head tail2)-  tail3 <- go 4 (head tail2).header 2000-  e 4035 (head tail3)-  tail4 <- go 5 (head tail2).header 150-  e 2185 (head tail4)-  sp1 <- splitPoint (head tail1) (head tail3)-  unless (sp1 == head start) $-    error $-      "Split point wrong between blocks 4031 and 4035: "-        ++ show sp1.height-  sp2 <- splitPoint (head tail4) (head tail3)-  unless (sp2 == head tail2) $-    error $-      "Split point wrong between blocks 2185 and 4035: "-        ++ show sp2.height-  where-    e n bn@BlockNode {} =-      unless (bn.height == n) $-        error $-          "Node height "-            ++ show bn.height-            ++ " of first chunk should be "-            ++ show n-    go seed start n = do-      let bhs = appendBlocks net seed start n-      bnE <- connectBlocks net myTime bhs-      case bnE of-        Right bn -> return bn-        Left ex -> error ex--{- Merkle Trees -}--testTreeWidth :: Int -> Property-testTreeWidth i = i /= 0 ==> calcTreeWidth (abs i) (calcTreeHeight $ abs i) == 1--testBaseWidth :: Int -> Property-testBaseWidth i = i /= 0 ==> calcTreeWidth (abs i) 0 == abs i--buildExtractTree :: Network -> [(TxHash, Bool)] -> Bool-buildExtractTree net txs =-  r == buildMerkleRoot (map fst txs) && m == map fst (filter snd txs)-  where-    (f, h) = buildPartialMerkle txs-    (r, m) =-      fromRight (error "Could not extract matches from Merkle tree") $-        extractMatches net f h (length txs)--testCompact :: Assertion-testCompact = do-  assertEqual "vector 1" 0x05123456 (encodeCompact 0x1234560000)-  assertEqual "vector 2" (0x1234560000, False) (decodeCompact 0x05123456)-  assertEqual "vector 3" 0x0600c0de (encodeCompact 0xc0de000000)-  assertEqual "vector 4" (0xc0de000000, False) (decodeCompact 0x0600c0de)-  assertEqual "vector 5" 0x05c0de00 (encodeCompact (-0x40de000000))-  assertEqual "vector 6" (-0x40de000000, False) (decodeCompact 0x05c0de00)--testCompactBitcoinCore :: Assertion-testCompactBitcoinCore = do-  assertEqual "zero" (0, False) (decodeCompact 0x00000000)-  assertEqual-    "zero (encode · decode)"-    0x00000000-    (encodeCompact . fst $ decodeCompact 0x00000000)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x00123456)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x01003456)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x02000056)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x03000000)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x04000000)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x00923456)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x01803456)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x02800056)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x03800000)-  assertEqual "rounds to zero" (0, False) (decodeCompact 0x04800000)-  assertEqual "vector 1 (decode)" (0x12, False) (decodeCompact 0x01123456)-  assertEqual-    "vector 1 (encode · decode)"-    0x01120000-    (encodeCompact . fst $ decodeCompact 0x01123456)-  assertEqual "0x80 bit set" 0x02008000 (encodeCompact 0x80)-  assertEqual-    "vector 2 (negative) (decode)"-    (-0x7e, False)-    (decodeCompact 0x01fedcba)-  assertEqual-    "vector 2 (negative) (encode · decode)"-    0x01fe0000-    (encodeCompact . fst $ decodeCompact 0x01fedcba)-  assertEqual "vector 3 (decode)" (0x1234, False) (decodeCompact 0x02123456)-  assertEqual-    "vector 3 (encode · decode)"-    0x02123400-    (encodeCompact . fst $ decodeCompact 0x02123456)-  assertEqual "vector 4 (decode)" (0x123456, False) (decodeCompact 0x03123456)-  assertEqual-    "vector 4 (encode · decode)"-    0x03123456-    (encodeCompact . fst $ decodeCompact 0x03123456)-  assertEqual-    "vector 5 (decode)"-    (0x12345600, False)-    (decodeCompact 0x04123456)-  assertEqual-    "vector 5 (encode · decode)"-    0x04123456-    (encodeCompact . fst $ decodeCompact 0x04123456)-  assertEqual-    "vector 6 (decode)"-    (-0x12345600, False)-    (decodeCompact 0x04923456)-  assertEqual-    "vector 6 (encode · decode)"-    0x04923456-    (encodeCompact . fst $ decodeCompact 0x04923456)-  assertEqual-    "vector 7 (decode)"-    (0x92340000, False)-    (decodeCompact 0x05009234)-  assertEqual-    "vector 7 (encode · decode)"-    0x05009234-    (encodeCompact . fst $ decodeCompact 0x05009234)-  assertEqual-    "vector 8 (decode)"-    ( 0x1234560000000000000000000000000000000000000000000000000000000000,-      False-    )-    (decodeCompact 0x20123456)-  assertEqual-    "vector 8 (encode · decode)"-    0x20123456-    (encodeCompact . fst $ decodeCompact 0x20123456)-  assertBool "vector 9 (decode) (overflow)" (snd $ decodeCompact 0xff123456)-  assertBool-    "vector 9 (decode) (positive)"-    ((> 0) . fst $ decodeCompact 0xff123456)--runMerkleVector :: (Text, [Text]) -> Assertion-runMerkleVector (r, hs) =-  assertBool "merkle vector" $-    buildMerkleRoot (map f hs) == (f r).get-  where-    f = fromJust . hexToTxHash--merkleVectors :: [(Text, [Text])]-merkleVectors =-  -- Block 000000000000cd7e8cf6510303dde76121a1a791c15dba0be4be7022b07cf9e1-  [ ( "fb6698ac95b754256c5e71b4fbe07638cb6ca83ee67f44e181b91727f09f4b1f",-      [ "dd96fdcfaec994bf583af650ff6022980ee0ba1686d84d0a3a2d24eabf34bc52",-        "1bc216f786a564378710ae589916fc8e092ddfb9f24fe6c47b733550d476d5d9",-        "a1db0b0194426064b067899ff2d975fb277fd52dbb1a38370800c76dd6503d41",-        "d69f7fb0e668fbd437d1bf5211cc34d7eb8746f50cfddf705fe10bc2f8f7035f",-        "5b4057cd80be7df5ed2ac42b776897ed3c26e3a01e4072075b8129c587094ef6",-        "ed6dabcfba0ef43c50d89a8a0e4b236b1bc6585d4c3bbf49728b55f44312d6bc",-        "056aaa9a3c635909c794e9b0acc7dccb0456c59a84c6b08417335bee4515e3d3",-        "05bae5f1d1c874171692e1fc06f664e63eb143d3f096601ef938e4a9012eee66",-        "b5e48e94e3f2fba197b3f591e01f47e185d7834d669529d44078e41c671aab0f",-        "3b56aeadfc0c5484fd507bc89f13f2e5f61c42e0a4ae9062eda9a9aeef7db6a4",-        "2affa187e1ebb94a2a86578b9f64951e854ff3d346fef259acfb6d0f5212e0d3"-      ]-    ),-    -- Block 00000000000007cc4b6f07bfed72bccc1ed8dd031a93969a4c22211f784457d4--    ( "886fea311d2dc64c315519f2d647e43998d780d2170f77e53dc0d85bf2ee680c",-      [ "c9c9e5211512629fd111cc071d745b8c79bf486b4ea95489eb5de08b5d786b8e",-        "20beb0ee30dfd323ade790ce9a46ae7a174f9ea44ce22a17c4d4eb23b7016f51",-        "d4cb7dd741e78a8f57e12f6c8ddb0361ff2a5bf9365bd7d7df761060847daf9a",-        "ddbfa6fdd29d4b47aeaadf82a4bf0a93d58cd7d8401fabf860a1ae8eeb51f42e",-        "9d82bafe44abee248b968c86f165051c8413482c232659795335c52922dab471",-        "86035372d31b53efd848cea7231aa9738c209aff64d3c59b1619341afb5b6ba3",-        "11e7a7393d9658813dfaebc04fa6d4b73bac8d641bffa7067da879523d43d030",-        "2f676b9aa5bc0ebf3395032c84c466e40cac29f80434cd1138e31c2d0fcc5c13",-        "37567d559fbfae07fda9a90de0ce30b202128bc8ebdfef5ad2b53e865a3478c2",-        "0b8e6c1200c454361e94e261738429e9c9b8dcffd85ec8511bbf5dc7e2e0ada8"-      ]-    ),-    -- Block 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048--    ( "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",-      ["0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"]-    ),-    -- Block 000000000004d160ac1f7b775d7c1823345aeadd5fcb29ca2ad2403bb7babd4c--    ( "aae018650f513fc42d55b2210ec3ceeeb194fb1261d37989de07451fc0cbac5c",-      [ "a4454f22831acd7904a9902c5070a3ee4bf4c2b13bc6b2dc66735dd3c4414028",-        "45297f334278885108dd38a0b689ed95a4373dd3f7e4413e6aebdc2654fb771b"-      ]-    ),-    -- Block 000000000001d1b13a7e86ddb20da178f20d6da5cd037a29c2a15b8b84cc774e--    ( "ca3580505feb87544760ac14a5859659e23be05f765bbed9f86a3c9aad1a5d0c",-      [ "60702384c6e9d34ff03c2b3e726bdc649befe603216815bd0a2974921d0d9549",-        "11f40f58941d2a81a1616a3b84b7dd8b9d07e68750827de488c11a18f54220bb",-        "d78e82527aa8cf16e375010bc666362c0258d3c0da1885a1871121706da8b633"-      ]-    ),-    -- Block 0000000000000630a4e2266a31776e952a19b7c99a6387917d9de9032f608021--    ( "dcce8be0a9a41e7bb726c5b49d957d90b5308e3dc5dce070ccbc8996e265a6c2",-      [ "c0f58ff12cd1023b05f8f7035cc62bf50958ddb216a4e0eb5471deb7ef25fe81",-        "24e5bbf9008641b8fcf3d076fef66c28c695362ba9f6a6042f8275a98414ee92",-        "e8e1f72abad5e34dabc0f6de46a484b17a9af857d1c41de19482fadf6f7f4b27",-        "540e4d34d9fd9e5ec02853054be7ad9260379bc23388489049cca1b0f7cf518a",-        "324444835c5fe0545f98c4240011b75e6ea1bb76f41829e4cfbe7f75b6cee924",-        "e7d31437ac21bceb0c222a82b2723e2b8a7654147e33397679f041537022a4b2",-        "a8b5768d8b33525ee89d546a6a6897f8e42ba9d56a2c5e871a5d2ab40258dc95",-        "7ba712b31bae8d45810a5cda3838c7e7fb9abd6e88bb4b3ee79be9ea2f714bb4",-        "2ae1c4d927b06edaa626b230976ad8062bbae24da9378d1de2409da5ab08a26d",-        "3c417dc8087d6878003624b74431e17fec9ca761389034b1b1e0f32cbfb11f4f",-        "de6de7beae8d8c98c7d46b4409d5460e58e3204d8b4caed256c7471998595909",-        "c7c3c211402b7c4379f7b01fadc67260ee58d11e8d0bcce3d68cb45f3467e99d",-        "77aa2717e727a096d81074bd46ae59462692d20a1acc1a01b2535518ae5aeb53",-        "4859a710bb673aca46208bbd59d1000ae990dafff5f70b56f0853aeeaea3948b",-        "38deca6991988e461b83aa0d49ffef0f304c4b760371682d152eeb8c56a48174",-        "648f4f50dada3574e2dfe2dc68956b01dd97d543859a3540bbe1ef5418d0e494",-        "9cd7be42c2f0cd8bf38738c162cd05108e213ec7958bf2571cb627872963f5c4",-        "6740e0dd8b97e23864af41839fc197238d2f0dbefce9a82c657556be65c465fa",-        "f75c2e4b70db4b0aabc44b77af1ae75d305340fcf6e7b5f806ddcba4aa42b55d",-        "e125c488636749da68e6696b97525a77146c0777c7946927e37afd513d74a4e6",-        "c20526f119aea10880af631eba7f0b60385a22e0b0c402fe8508d41952e58be9",-        "6456c023c7e245f5c57a168633a23f57f4fadb651115f807694a6bed14ae3b55",-        "98b26e364e2888c9f264e4b5e13103c89608609774eb07ce933d8a2a45d19776",-        "2efaa4f167bb65ba5684f8076cd9279fd67fd9c67388c8862809bab5542e637d",-        "ec44eeb84d8d976d77079a822710b4dfdb11a2d9a03d8cc00bab0ae424e84666",-        "410730d9f807d81ac48b8eafac6f1d36642c1c370241b367a35f0bac6ac7c05f",-        "e95a7d0d477fd3db22756a3fd390a50c7bc48dc9e946fea9d24bd0866b3bb0e9",-        "a72fec99d14939216628aaf7a0afc4c017113bcae964e777e6b508864eeaacc4",-        "8548433310fcf75dbbc042121e8318c678e0a017534786dd322a91cebe8d213f"-      ]-    )-  ]--testSubsidy :: Network -> Assertion-testSubsidy net = go (2 * 50 * 100 * 1000 * 1000) 0-  where-    go previous_subsidy halvings = do-      let height = halvings * net.halvingInterval-          subsidy = computeSubsidy net height-      if halvings >= 64-        then subsidy `shouldBe` 0-        else do-          subsidy `shouldBe` (previous_subsidy `div` 2)-          go subsidy (halvings + 1)--data AsertBlock = AsertBlock Int Integer Integer Word32--data AsertVector = AsertVector String Integer Integer Word32 [AsertBlock]--readAsertVector :: FilePath -> IO AsertVector-readAsertVector p = do-  (d : ah : apt : ab : _ : _ : _ : _ : xs) <- lines <$> readFile ("data/" ++ p)-  let desc = drop 16 d-      anchor_height = read (words ah !! 3)-      anchor_parent_time = read (words apt !! 4)-      anchor_nbits = read (words ab !! 3)-      blocks = map (f . words) (init xs)-  return $-    AsertVector-      desc-      anchor_height-      anchor_parent_time-      anchor_nbits-      blocks-  where-    f [i, h, t, g] = AsertBlock (read i) (read h) (read t) (read g)-    f _ = undefined--asertTests :: FilePath -> SpecWith ()-asertTests file = do-  v@(AsertVector d _ _ _ _) <- runIO $ readAsertVector file-  it d $ testAsertBits v--testAsertBits :: AsertVector -> Assertion-testAsertBits (AsertVector _ anchor_height anchor_parent_time anchor_bits blocks) =-  forM_ blocks $ \(AsertBlock _ h t g) ->-    computeAsertBits+{-# LANGUAGE ScopedTypeVariables #-}++module Haskoin.BlockSpec+  ( spec,+  )+where++import Control.Exception (evaluate)+import Control.Monad+import Control.Monad.State.Strict+import Data.Aeson (Value, decode, encode, object, toJSON, (.=))+import qualified Data.ByteString as B+import Data.Bytes.Get (runGetS)+import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (deserialize, serialize)+import Data.Default (def)+import Data.Either (isLeft)+import Data.List (isPrefixOf)+import Data.Maybe (fromJust)+import Data.String (fromString)+import Data.String.Conversions (cs)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Word (Word32)+import Haskoin.Block+import Haskoin.Crypto+import Haskoin.Network.Constants+import Haskoin.Network.Data+import Haskoin.Transaction+import Haskoin.Util+import Haskoin.Util.Arbitrary+import Test.HUnit hiding (State)+import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck+import Text.Printf (printf)++identityTests :: Ctx -> IdentityTests+identityTests ctx =+  def+    { readTests =+        [ ReadBox (flip arbitraryBlock ctx =<< arbitraryNetwork),+          ReadBox arbitraryBlockHash,+          ReadBox arbitraryBlockHeader,+          ReadBox arbitraryGetBlocks,+          ReadBox arbitraryGetHeaders,+          ReadBox arbitraryHeaders,+          ReadBox arbitraryMerkleBlock,+          ReadBox arbitraryBlockNode,+          ReadBox arbitraryHeaderMemory+        ],+      jsonTests =+        [ JsonBox (flip arbitraryBlock ctx =<< arbitraryNetwork),+          JsonBox arbitraryBlockHash,+          JsonBox arbitraryBlockHeader+        ],+      serialTests =+        [ SerialBox (flip arbitraryBlock ctx =<< arbitraryNetwork),+          SerialBox arbitraryBlockHash,+          SerialBox arbitraryBlockHeader,+          SerialBox arbitraryGetBlocks,+          SerialBox arbitraryGetHeaders,+          SerialBox arbitraryHeaders,+          SerialBox arbitraryMerkleBlock,+          SerialBox arbitraryBlockNode+        ]+    }++-- | Current time given to the validation functions. It is years after the+-- regtest genesis block, so the two-hour rule never interferes with the test+-- chains unless a test passes its own time.+myTime :: Timestamp+myTime = 1499083075++withChain :: Network -> State HeaderMemory a -> a+withChain net f = evalState f (initialChain net)++chain :: (BlockHeaders m) => Network -> BlockHeader -> Int -> m ()+chain net bh i = do+  bnsE <- connectBlocks net myTime bhs+  either error (const $ return ()) bnsE+  where+    bhs = appendBlocks net 6 bh i++-- | Regtest with a retarget every ten blocks and no min-difficulty rule, so+-- that retargets can be tested on short chains.+retargetNet :: Network+retargetNet =+  (btcRegTest :: Network)+    { powNoRetarget = False,+      minDiffBlocks = False,+      targetTimespan = 10 * 600+    }++-- | Regtest with the BIP94 rules, the min-difficulty rule and a retarget every+-- ten blocks.+bip94Net :: Network+bip94Net =+  (btcRegTest :: Network)+    { bip94 = True,+      powNoRetarget = False,+      targetTimespan = 10 * 600+    }++-- | The same network without the BIP94 rules.+noBip94Net :: Network+noBip94Net = (bip94Net :: Network) {bip94 = False}++-- | Bitcoin Cash regtest that retargets, with no Bitcoin Cash algorithm+-- enabled and no checkpoint on its genesis block. Each test enables the+-- algorithm it needs.+cashNet :: Network+cashNet =+  (bchRegTest :: Network)+    { powNoRetarget = False,+      minDiffBlocks = False,+      edaHeight = Nothing,+      daaHeight = Nothing,+      asertActivationTime = Nothing,+      checkpoints = []+    }++edaNet :: Network+edaNet = (cashNet :: Network) {edaHeight = Just 1}++daaNet :: Network+daaNet = (cashNet :: Network) {daaHeight = Just 1}++-- | ASERT activates when the median time past reaches the genesis time plus+-- 6000 seconds. With one block every 600 seconds that is the median time past+-- of block 15.+asertActivation :: Timestamp+asertActivation = cashNet.genesisHeader.timestamp + 6000++asertNet :: Network+asertNet =+  (cashNet :: Network)+    { asertActivationTime = Just asertActivation,+      asertHalfLife = 2 * 24 * 60 * 60+    }++-- | A version 4 header on top of the parent with the given timestamp and+-- bits, and a zero nonce. Version 4 passes the BIP34, BIP65 and BIP66 version+-- rules at any height, which regtest applies from height 1 like Bitcoin Core.+headerOn :: BlockHeader -> Timestamp -> Word32 -> BlockHeader+headerOn parent time bits =+  (parent :: BlockHeader) {version = 4, prev = headerHash parent, timestamp = time, bits = bits, nonce = 0}++-- | Mine a header on top of the parent with the given timestamp and bits.+mineOn :: Network -> BlockHeader -> Timestamp -> Word32 -> BlockHeader+mineOn net parent time bits = mineBlock net 0 (headerOn parent time bits)++-- | The same header with another version number.+headerWithVersion :: Word32 -> BlockHeader -> BlockHeader+headerWithVersion v (BlockHeader _ p m t b n) = BlockHeader v p m t b n++-- | Mine a chain of headers with the given timestamps and bits.+mineChain :: Network -> BlockHeader -> [(Timestamp, Word32)] -> [BlockHeader]+mineChain net parent = drop 1 . scanl (\p (time, bits) -> mineOn net p time bits) parent++-- | Put a chain of unvalidated headers in the store on top of the best block,+-- one per (timestamp, bits) pair. Heights, work and skip pointers are filled+-- in the way 'connectBlocks' does, but there is no proof of work and no rule+-- check, so difficulty tests can use any bits without mining. Returns the new+-- nodes, oldest first, and leaves the last one as the best block.+storeChain :: [(Timestamp, Word32)] -> State HeaderMemory [BlockNode]+storeChain specs = do+  start <- getBestBlockHeader+  go start specs+  where+    go _ [] = return []+    go par ((time, bits) : rest) = do+      let hdr = headerOn par.header time bits+          h = par.height + 1+      skipNode <- fromJust <$> getAncestor (skipHeight h) par+      let node =+            BlockNode+              { header = hdr,+                height = h,+                work = par.work + headerWork hdr,+                skip = headerHash skipNode.header+              }+      addBlockHeader node+      setBestBlockHeader node+      (node :) <$> go node rest++-- | Timestamps and bits of a chain of evenly spaced blocks after genesis.+evenlySpaced :: Network -> Word32 -> Int -> Word32 -> [(Timestamp, Word32)]+evenlySpaced net spacing n bits =+  [(net.genesisHeader.timestamp + spacing * fromIntegral i, bits) | i <- [1 .. n]]++-- | The work required for a block with the given timestamp on top of the+-- stored chain.+requiredAfter :: Network -> Timestamp -> State HeaderMemory Word32+requiredAfter net time = do+  par <- getBestBlockHeader+  nextWorkRequired net par (headerOn par.header time par.header.bits)++connectHeight :: Network -> Timestamp -> BlockHeader -> State HeaderMemory (Either String BlockHeight)+connectHeight net t h = fmap (.height) <$> connectBlock net t h++connectAll :: Network -> [BlockHeader] -> State HeaderMemory ()+connectAll net hs = connectBlocks net myTime hs >>= either error (const (return ()))++genesisHash :: BlockHash+genesisHash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"++-- Three distinct transaction hashes for Merkle tree tests.+txA, txB, txC :: TxHash+txA = TxHash (sha256 ("a" :: B.ByteString))+txB = TxHash (sha256 ("b" :: B.ByteString))+txC = TxHash (sha256 ("c" :: B.ByteString))++readBlock :: FilePath -> IO Block+readBlock path = do+  hex <- T.strip . cs <$> B.readFile path+  either error return $ runGetS deserialize $ fromJust $ decodeHex hex++displayTxHash :: Text -> TxHash+displayTxHash = fromJust . hexToTxHash++spec :: Spec+spec = prepareContext $ \ctx -> do+  testIdentity $ identityTests ctx+  blockHashSpec+  blockHeaderSpec+  compactSpec+  serializationLimitSpec+  proofOfWorkSpec+  genesisSpec+  headerStoreSpec+  validationSpec+  medianTimeSpec+  bitcoinDifficultySpec+  cashDifficultySpec+  asertSpec+  checkpointSpec+  merkleSpec+  describe "block subsidy" $ do+    it "computes bitcoin block subsidy correctly" (testSubsidy btc)+    it "computes regtest block subsidy correctly" (testSubsidy btcRegTest)++blockHashSpec :: Spec+blockHashSpec = describe "block hash" $ do+  prop "encodes and decodes block hash" $+    forAll arbitraryBlockHash $ \h ->+      hexToBlockHash (blockHashToHex h) == Just h+  prop "from string block hash" $+    forAll arbitraryBlockHash $ \h ->+      fromString (cs $ blockHashToHex h) == h+  it "hashes the genesis header" $+    headerHash btc.genesisHeader `shouldBe` genesisHash+  it "shows a block hash in display order" $+    blockHashToHex genesisHash+      `shouldBe` "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"+  it "serializes a block hash in internal byte order" $+    encodeHex (runPutS (serialize genesisHash))+      `shouldBe` "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000"+  it "encodes a block hash to JSON in display order" $+    encode genesisHash+      `shouldBe` "\"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f\""+  it "rejects hex that is not 32 bytes" $ do+    hexToBlockHash (T.replicate 31 "00") `shouldBe` Nothing+    hexToBlockHash (T.replicate 33 "00") `shouldBe` Nothing+    hexToBlockHash ("ff" <> T.replicate 32 "00") `shouldBe` Nothing+    hexToBlockHash (T.replicate 32 "zz") `shouldBe` Nothing+    hexToBlockHash "" `shouldBe` Nothing++blockHeaderSpec :: Spec+blockHeaderSpec = describe "block header" $ do+  it "serializes the genesis header" $+    encodeHex (runPutS (serialize btc.genesisHeader))+      `shouldBe` "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"+  it "encodes the genesis header to JSON like the getblockheader RPC" $+    toJSON btc.genesisHeader `shouldBe` genesisHeaderJson+  it "encodes the same JSON with the streaming encoder" $+    decode (encode btc.genesisHeader) `shouldBe` Just genesisHeaderJson+  it "decodes the genesis header from JSON" $+    decode (encode genesisHeaderJson) `shouldBe` Just btc.genesisHeader+  it "rejects a merkle root that is not 32 bytes in JSON" $+    (decode (encode (object [("version", toJSON (1 :: Int)), ("prevblock", toJSON (T.replicate 64 "0")), ("merkleroot", toJSON (T.replicate 66 "0")), ("timestamp", toJSON (0 :: Int)), ("bits", toJSON (0 :: Int)), ("nonce", toJSON (0 :: Int))])) :: Maybe BlockHeader)+      `shouldBe` Nothing+  it "serializes a getheaders message" $+    -- version 70015 = 0x0001117f, one locator hash, stop hash of zeroes+    encodeHex (runPutS (serialize (GetHeaders 70015 [genesisHash] "0000000000000000000000000000000000000000000000000000000000000000")))+      `shouldBe` "7f110100016fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000000000000000000000000000000000000000000000000000000000000"+  where+    genesisHeaderJson :: Value+    genesisHeaderJson =+      object+        [ "version" .= (1 :: Int),+          "prevblock" .= ("0000000000000000000000000000000000000000000000000000000000000000" :: Text),+          "merkleroot" .= ("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" :: Text),+          "timestamp" .= (1231006505 :: Int),+          "bits" .= (486604799 :: Int),+          "nonce" .= (2083236893 :: Int)+        ]++compactSpec :: Spec+compactSpec = describe "compact number" $ do+  it "compact number local vectors" testCompact+  it "compact number imported vectors" testCompactBitcoinCore+  it "refuses a number wider than 255 bytes" $+    evaluate (encodeCompact (2 ^ (2048 :: Int))) `shouldThrow` anyErrorCall+  it "encodes the largest 255-byte number" $+    -- 255 bytes of 0xff: the top byte has the sign bit set, so the mantissa+    -- shifts down one byte and the exponent becomes 256, which cannot be+    -- represented either+    evaluate (encodeCompact (2 ^ (2040 :: Int) - 1)) `shouldThrow` anyErrorCall++serializationLimitSpec :: Spec+serializationLimitSpec = describe "serialization limits" $ do+  it "refuses a block whose transaction count wraps around" $+    -- count 0xffffffffffffffff would become -1 as an Int and read as no transactions+    (runGetS deserialize (genesisHeaderBytes <> B.pack (0xff : replicate 8 0xff)) :: Either String Block)+      `shouldSatisfy` isLeft+  it "refuses a headers message with more than 33554432 entries" $+    -- count 0x02000001 as a four-byte varint+    (runGetS deserialize (B.pack [0xfe, 0x01, 0x00, 0x00, 0x02]) :: Either String Headers)+      `shouldSatisfy` isLeft+  it "refuses a merkle block whose hash count wraps around" $+    (runGetS deserialize (genesisHeaderBytes <> B.pack [1, 0, 0, 0] <> B.pack (0xff : replicate 8 0xff)) :: Either String MerkleBlock)+      `shouldSatisfy` isLeft+  it "reads a block with no transactions when the count is zero" $+    (runGetS deserialize (genesisHeaderBytes <> B.pack [0]) :: Either String Block)+      `shouldBe` Right (Block btc.genesisHeader [])+  where+    genesisHeaderBytes = runPutS (serialize btc.genesisHeader)++proofOfWorkSpec :: Spec+proofOfWorkSpec = describe "proof of work" $ do+  it "accepts the genesis header" $+    isValidPOW btc btc.genesisHeader `shouldBe` True+  it "rejects a header whose hash is above its target" $+    -- a 32-bit nonce change leaves a hash with 32 leading zero bits with probability 2^-32+    isValidPOW btc (btc.genesisHeader :: BlockHeader) {nonce = 0} `shouldBe` False+  it "rejects a target above the proof of work limit" $+    -- exponent 0x1e makes the target 256 times the limit+    isValidPOW btc (btc.genesisHeader :: BlockHeader) {bits = 0x1e00ffff} `shouldBe` False+  it "rejects a negative target" $+    isValidPOW btc (btc.genesisHeader :: BlockHeader) {bits = 0x03800001} `shouldBe` False+  it "rejects an overflowing target" $+    isValidPOW btc (btc.genesisHeader :: BlockHeader) {bits = 0xff123456} `shouldBe` False+  it "rejects a zero target" $+    isValidPOW btc (btc.genesisHeader :: BlockHeader) {bits = 0} `shouldBe` False+  it "reads the hash as a number" $+    blockPOW genesisHash `shouldBe` 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f+  it "counts the work of the genesis header" $+    -- 2^256 / (0xffff * 2^208 + 1) = 4295032833, Bitcoin Core's 0x0000000100010001+    headerWork btc.genesisHeader `shouldBe` 4295032833+  it "counts the work of a regtest header" $+    -- 2^256 / (0x7fffff * 2^232 + 1) = 2+    headerWork btcRegTest.genesisHeader `shouldBe` 2+  it "gives no work to a negative target" $+    headerWork (btc.genesisHeader :: BlockHeader) {bits = 0x03800001} `shouldBe` 0+  it "gives no work to an overflowing target" $+    headerWork (btc.genesisHeader :: BlockHeader) {bits = 0xff123456} `shouldBe` 0+  it "gives no work to a zero target" $+    headerWork (btc.genesisHeader :: BlockHeader) {bits = 0} `shouldBe` 0+  it "mines a regtest header by changing only the nonce" $ do+    let hdr = headerOn btcRegTest.genesisHeader (btcRegTest.genesisHeader.timestamp + 600) 0x207fffff+        mined = mineBlock btcRegTest 7 hdr+    isValidPOW btcRegTest mined `shouldBe` True+    (mined :: BlockHeader) {nonce = 0} `shouldBe` hdr+    mined.nonce `shouldSatisfy` (>= 7)+  it "appends mined blocks one target spacing apart" $ do+    let hs = appendBlocks btcRegTest 1 btcRegTest.genesisHeader 3+    map (.timestamp) hs `shouldBe` [1296689202, 1296689802, 1296690402]+    map (.prev) hs `shouldBe` headerHash btcRegTest.genesisHeader : map headerHash (take 2 hs)+    all (isValidPOW btcRegTest) hs `shouldBe` True++genesisSpec :: Spec+genesisSpec = describe "genesis blocks" $ do+  it "has the BIP94 genesis block" $+    headerHash btcTest4.genesisHeader+      `shouldBe` "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043"+  it "has the Bitcoin Cash testnet4 genesis block" $+    headerHash bchTest4.genesisHeader+      `shouldBe` "000000001dd410c49a788668ce26751718cc797474d3152a5fc073dd44fd9f7b"+  it "serializes the mainnet coinbase" $+    encodeHex (runPutS (serialize mainnetGenesisTx))+      `shouldBe` "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"+  it "serializes the testnet4 coinbase" $+    encodeHex (runPutS (serialize testnet4GenesisTx))+      `shouldBe` "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5504ffff001d01044c4c30332f4d61792f323032342030303030303030303030303030303030303030303165626435386332343439373062336161396437383362623030313031316662653865613865393865303065ffffffff0100f2052a010000002321000000000000000000000000000000000000000000000000000000000000000000ac00000000"+  it "hashes the testnet4 coinbase to its merkle root" $+    txHashToHex (txHash testnet4GenesisTx)+      `shouldBe` "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e"+  it "pairs every network's genesis header with its coinbase" $+    forM_ allNets $ \net ->+      (net.name, (txHash net.genesisTx).get) `shouldBe` (net.name, net.genesisHeader.merkle)+  it "builds the genesis block of every network" $+    forM_ allNets $ \net ->+      genesisBlock net `shouldBe` Block net.genesisHeader [net.genesisTx]+  it "builds the genesis node" $ do+    let node = genesisNode btc+    node.height `shouldBe` 0+    node.work `shouldBe` 4295032833+    node.skip `shouldBe` genesisHash+    isGenesis node `shouldBe` True++headerStoreSpec :: Spec+headerStoreSpec = describe "header store" $ do+  it "starts with genesis as the best block" $+    withChain btc getBestBlockHeader `shouldBe` genesisNode btc+  it "finds genesis by hash" $+    withChain btc (getBlockHeader genesisHash) `shouldBe` Just (genesisNode btc)+  it "finds nothing for an unknown hash" $+    withChain btc (getBlockHeader "0000000000000000000000000000000000000000000000000000000000000001") `shouldBe` Nothing+  it "finds an added node by hash" $+    withChain btcRegTest (storeChain (evenlySpaced btcRegTest 600 3 0x207fffff) >>= \ns -> getBlockHeader (headerHash (last ns).header))+      `shouldSatisfy` maybe False ((== 3) . (.height))+  it "takes the short hash from the high-entropy end of the hash" $+    -- the first eight bytes of the internal order, as a big-endian number+    shortBlockHash genesisHash `shouldBe` 0x6fe28c0ab6f1b372+  it "gets best block on bchRegTest" $+    let net = bchRegTest+        bb =+          withChain net $ do+            chain net net.genesisHeader 100+            getBestBlockHeader+     in bb.height `shouldBe` 100+  it "computes skip heights like Bitcoin Core" $+    map skipHeight [0 .. 12] `shouldBe` [0, 0, 0, 1, 0, 1, 4, 1, 0, 1, 8, 1, 8]+  it "gets ancestors at any height" $+    let heights =+          withChain btcRegTest $ do+            chain btcRegTest btcRegTest.genesisHeader 100+            bb <- getBestBlockHeader+            forM [0, 1, 37, 64, 99, 100] $ \h -> fmap (.height) <$> getAncestor h bb+     in heights `shouldBe` map Just [0, 1, 37, 64, 99, 100]+  it "has no ancestor above the block" $+    withChain btcRegTest (chain btcRegTest btcRegTest.genesisHeader 5 >> getBestBlockHeader >>= getAncestor 6)+      `shouldBe` Nothing+  it "finds the parent of a block" $+    let hdr = mineOn btcRegTest btcRegTest.genesisHeader (btcRegTest.genesisHeader.timestamp + 600) 0x207fffff+     in withChain btcRegTest (parentBlock hdr) `shouldBe` Just (genesisNode btcRegTest)+  it "lists parents from the nearest, stopping at genesis" $+    let heights = withChain btcRegTest $ do+          chain btcRegTest btcRegTest.genesisHeader 5+          bb <- getBestBlockHeader+          map (.height) <$> getParents 10 bb+     in heights `shouldBe` [4, 3, 2, 1, 0]+  it "lists the requested number of parents" $+    let heights = withChain btcRegTest $ do+          chain btcRegTest btcRegTest.genesisHeader 20+          bb <- getBestBlockHeader+          map (.height) <$> getParents 3 bb+     in heights `shouldBe` [19, 18, 17]+  it "builds a block locator like Bitcoin Core" $+    -- ten single steps below the tip, then steps of 2, 4, 8, ... and genesis+    locatorHeights 100 `shouldBe` [100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 87, 83, 75, 59, 27, 0]+  it "ends the block locator at genesis once" $+    locatorHeights 12 `shouldBe` [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]+  it "builds a block locator of a short chain" $+    locatorHeights 3 `shouldBe` [3, 2, 1, 0]+  it "builds a block locator of hashes" $+    let (hashes, nodes) = withChain btcRegTest $ do+          chain btcRegTest btcRegTest.genesisHeader 20+          bb <- getBestBlockHeader+          (,) <$> blockLocator bb <*> blockLocatorNodes bb+     in hashes `shouldBe` map (headerHash . (.header)) nodes+  it "follows split chains on bchRegTest" $+    let net = bchRegTest+        bb = withChain net $ splitChain net >> getBestBlockHeader+     in bb.height `shouldBe` 4035+  it "prefers the block with more work" $ do+    let node w h = (genesisNode btc :: BlockNode) {work = w, height = h}+    chooseBest (node 10 1) (node 20 1) `shouldBe` node 20 1+    chooseBest (node 20 1) (node 10 5) `shouldBe` node 20 1+  it "prefers the higher block on equal work" $ do+    let node w h = (genesisNode btc :: BlockNode) {work = w, height = h}+    chooseBest (node 10 1) (node 10 2) `shouldBe` node 10 2+    chooseBest (node 10 2) (node 10 1) `shouldBe` node 10 2+  it "keeps the first block on equal work and height" $ do+    let node w h hdr = (genesisNode btc :: BlockNode) {work = w, height = h, header = hdr}+        first = node 10 1 btc.genesisHeader+        second = node 10 1 (btc.genesisHeader :: BlockHeader) {nonce = 1}+    chooseBest first second `shouldBe` first+    chooseBest second first `shouldBe` second+  it "orders nodes by height and then by header" $ do+    let node h hdr = (genesisNode btc :: BlockNode) {height = h, header = hdr}+        a = node 1 btc.genesisHeader+        b = node 1 (btc.genesisHeader :: BlockHeader) {nonce = 4294967295}+        c = node 2 btc.genesisHeader+    compare a b `shouldBe` LT+    compare a c `shouldBe` LT+    compare a a `shouldBe` EQ+  it "searches the chain for the first block at or above a height" $+    searchHeights `shouldBe` (Just 7, Just 7, Nothing, Just 20, Just 0, Nothing)+  where+    locatorHeights n = withChain btcRegTest $ do+      chain btcRegTest btcRegTest.genesisHeader n+      bb <- getBestBlockHeader+      map (.height) <$> blockLocatorNodes bb+    searchHeights = withChain btcRegTest $ do+      _ <- storeChain (evenlySpaced btcRegTest 600 20 0x207fffff)+      let byHeight :: BlockHeight -> BlockNode -> State HeaderMemory Ordering+          byHeight h bn = return (compare bn.height h)+          allBelow, allAbove :: BlockNode -> State HeaderMemory Ordering+          allBelow bn = return (compare bn.height 100)+          allAbove bn = return (compare (bn.height + 100) 50)+      a <- fmap (.height) <$> firstGreaterOrEqual btcRegTest (byHeight 7)+      b <- fmap (.height) <$> lastSmallerOrEqual btcRegTest (byHeight 7)+      c <- fmap (.height) <$> firstGreaterOrEqual btcRegTest allBelow+      d <- fmap (.height) <$> lastSmallerOrEqual btcRegTest allBelow+      e <- fmap (.height) <$> firstGreaterOrEqual btcRegTest allAbove+      f <- fmap (.height) <$> lastSmallerOrEqual btcRegTest allAbove+      return (a, b, c, d, e, f)++validationSpec :: Spec+validationSpec = describe "header validation" $ do+  let net = btcRegTest+      t0 = net.genesisHeader.timestamp+      b1 = mineOn net net.genesisHeader (t0 + 600) 0x207fffff+      b2 = mineOn net b1 (t0 + 1200) 0x207fffff+      b3 = mineOn net b2 (t0 + 1800) 0x207fffff+  it "connects a chain and returns the newest node first" $+    let heights = withChain net $ do+          r <- connectBlocks net myTime [b1, b2, b3]+          return (map (.height) <$> r)+     in heights `shouldBe` Right [3, 2, 1]+  it "connects headers one at a time" $+    withChain net (mapM (connectHeight net myTime) [b1, b2, b3]) `shouldBe` [Right 1, Right 2, Right 3]+  it "makes the connected chain the best block" $+    withChain net (connectAll net [b1, b2, b3] >> getBestBlockHeader) `shouldSatisfy` (== 3) . (.height)+  it "accepts a header it already has" $+    withChain net (connectAll net [b1] >> connectHeight net myTime b1) `shouldBe` Right 1+  it "rejects a header whose parent is unknown" $+    withChain net (connectHeight net myTime b2) `shouldBe` Left "Could not get parent block"+  it "rejects a list of headers that is not a chain" $+    withChain net (connectBlocks net myTime [b1, b3]) `shouldBe` Left "Blocks to connect do not form a chain"+  it "keeps the headers before a bad one and the best block among them" $+    let bad = mineOn net b2 (t0 + 1800) 0x1f7fffff+        (result, best, stored) = withChain net $ do+          r <- connectBlocks net myTime [b1, b2, bad]+          bb <- getBestBlockHeader+          s <- getBlockHeader (headerHash b2)+          return (map (.height) <$> r, bb.height, (.height) <$> s)+     in (result, best, stored)+          `shouldBe` (Left "Incorrect difficulty bits: 0x1f7fffff, expected 0x207fffff", 2, Just 2)+  it "rejects a header whose proof of work fails" $+    -- a nonce of zero on top of the mainnet genesis block has 32 leading zero bits with probability 2^-32+    withChain btc (connectBlock btc myTime (headerOn btc.genesisHeader (btc.genesisHeader.timestamp + 600) 0x1d00ffff))+      `shouldSatisfy` either ("Proof of work failed: " `isPrefixOf`) (const False)+  it "rejects a header whose bits differ from the required difficulty" $+    -- regtest requires 0x207fffff; the header is mined at a harder target+    withChain net (connectHeight net myTime (mineOn net net.genesisHeader (t0 + 600) 0x1f7fffff))+      `shouldBe` Left "Incorrect difficulty bits: 0x1f7fffff, expected 0x207fffff"+  it "rejects a timestamp more than two hours in the future" $+    -- current time 1296700000, header at 1296707201 is 7201 seconds ahead+    withChain net (connectHeight net 1296700000 (mineOn net net.genesisHeader 1296707201 0x207fffff))+      `shouldBe` Left "Invalid header timestamp: 1296707201"+  it "accepts a timestamp exactly two hours in the future" $+    withChain net (connectHeight net 1296700000 (mineOn net net.genesisHeader 1296707200 0x207fffff))+      `shouldBe` Right 1+  it "rejects a timestamp equal to the median time past" $+    -- the median of genesis (1296688602) and block 1 (1296689202) is 1296689202+    withChain net (connectAll net [b1] >> connectHeight net myTime (mineOn net b1 (t0 + 600) 0x207fffff))+      `shouldBe` Left "Block timestamp too early: 1296689202"+  it "accepts a timestamp one second above the median time past" $+    withChain net (connectAll net [b1] >> connectHeight net myTime (mineOn net b1 (t0 + 601) 0x207fffff))+      `shouldBe` Right 2+  it "rejects a block that does not match a checkpoint" $+    let cpNet = (net :: Network) {checkpoints = [(2, headerHash b3)]}+     in withChain cpNet (connectAll cpNet [b1] >> connectHeight cpNet myTime b2)+          `shouldBe` Left "Rejected checkpoint: 2"+  it "accepts a block that matches a checkpoint" $+    let cpNet = (net :: Network) {checkpoints = [(2, headerHash b2)]}+     in withChain cpNet (connectAll cpNet [b1] >> connectHeight cpNet myTime b2)+          `shouldBe` Right 2+  it "rejects a fork below the last checkpoint reached" $+    let cpNet = (net :: Network) {checkpoints = [(2, headerHash b2)]}+        fork2 = mineOn net b1 (t0 + 1201) 0x207fffff+     in withChain cpNet (connectAll cpNet [b1, b2, b3] >> connectHeight cpNet myTime fork2)+          `shouldBe` Left "Rewriting pre-checkpoint chain: 2"+  it "accepts a fork above the last checkpoint reached" $+    let cpNet = (net :: Network) {checkpoints = [(2, headerHash b2)]}+        fork3 = mineOn net b2 (t0 + 1801) 0x207fffff+     in withChain cpNet (connectAll cpNet [b1, b2, b3] >> connectHeight cpNet myTime fork3)+          `shouldBe` Right 3+  it "rejects the BIP34 activation block with the wrong hash" $+    let bipNet = (net :: Network) {bip34Height = 2, bip34Hash = Just (headerHash b3)}+     in withChain bipNet (connectAll bipNet [b1] >> connectHeight bipNet myTime b2)+          `shouldSatisfy` either ("Rejected BIP-34 block: " `isPrefixOf`) (const False)+  it "accepts the BIP34 activation block with the right hash" $+    let bipNet = (net :: Network) {bip34Height = 2, bip34Hash = Just (headerHash b2)}+     in withChain bipNet (connectAll bipNet [b1] >> connectHeight bipNet myTime b2)+          `shouldBe` Right 2+  it "accepts any block at the BIP34 height when no hash is pinned" $+    let bipNet = (net :: Network) {bip34Height = 2, bip34Hash = Nothing}+     in withChain bipNet (connectAll bipNet [b1] >> connectHeight bipNet myTime b2)+          `shouldBe` Right 2+  it "rejects version 1 once BIP34 is active" $+    let bipNet = (net :: Network) {bip34Height = 0, bip34Hash = Nothing}+        hdr = mineBlock net 0 (headerWithVersion 1 (headerOn net.genesisHeader (t0 + 600) 0x207fffff))+     in withChain bipNet (connectHeight bipNet myTime hdr) `shouldBe` Left "Invalid block version: 1"+  it "rejects a negative version once BIP34 is active" $+    let bipNet = (net :: Network) {bip34Height = 0, bip34Hash = Nothing}+        hdr = mineBlock net 0 (headerWithVersion 0xffffffff (headerOn net.genesisHeader (t0 + 600) 0x207fffff))+     in withChain bipNet (connectHeight bipNet myTime hdr) `shouldBe` Left "Invalid block version: 4294967295"+  it "rejects version 2 once BIP66 is active" $+    let bipNet = (net :: Network) {bip34Height = 0, bip34Hash = Nothing, bip66Height = 1}+        hdr = mineBlock net 0 (headerWithVersion 2 (headerOn net.genesisHeader (t0 + 600) 0x207fffff))+     in withChain bipNet (connectHeight bipNet myTime hdr) `shouldBe` Left "Invalid block version: 2"+  it "accepts version 4 everywhere" $+    let bipNet = (net :: Network) {bip34Height = 0, bip34Hash = Nothing, bip66Height = 1, bip65Height = 1}+        hdr = mineBlock net 0 (headerWithVersion 4 (headerOn net.genesisHeader (t0 + 600) 0x207fffff))+     in withChain bipNet (connectHeight bipNet myTime hdr) `shouldBe` Right 1+  it "keeps the first block seen on equal work and height" $+    let c1 = mineOn net net.genesisHeader (t0 + 601) 0x207fffff+        tips = withChain net $ do+          connectAll net [b1]+          _ <- connectBlock net myTime c1+          s1 <- getBestBlockHeader+          connectAll net [c1]+          s2 <- getBestBlockHeader+          connectAll net [b1]+          s3 <- getBestBlockHeader+          return [s1, s2, s3]+     in map (headerHash . (.header)) tips `shouldBe` replicate 3 (headerHash b1)+  it "moves the best block to the fork with more work" $+    let c1 = mineOn net net.genesisHeader (t0 + 601) 0x207fffff+        c2 = mineOn net c1 (t0 + 1201) 0x207fffff+        tips = withChain net $ do+          connectAll net [b1]+          connectAll net [c1, c2]+          s1 <- getBestBlockHeader+          connectAll net [b2, b3]+          s2 <- getBestBlockHeader+          return [s1, s2]+     in map (headerHash . (.header)) tips `shouldBe` [headerHash c2, headerHash b3]+  describe "BIP94" $ do+    it "rejects the first block of a retarget period when it is too early" $ do+      -- Blocks 1 to 8 are ten minutes apart; block 9 jumps two hours ahead.+      let t = bip94Net.genesisHeader.timestamp+          hs =+            mineChain bip94Net bip94Net.genesisHeader $+              [(t + 600 * i, 0x207fffff) | i <- [1 .. 8]] ++ [(t + 12000, 0x207fffff)]+          par = last hs+          b10 = mineOn bip94Net par (t + 11400) 0x207fffff+          connect net' chain' h = withChain net' $ do+            connectBlocks net' myTime chain' >>= either error (const (return ()))+            connectHeight net' myTime h+      -- Block 10 starts a retarget period, so it may be at most 600 seconds+      -- earlier than block 9.+      connect bip94Net hs (mineOn bip94Net par (t + 11399) 0x207fffff)+        `shouldBe` Left "Block timestamp too early for retarget: 1296700001"+      connect bip94Net hs b10 `shouldBe` Right 10+      -- Block 11 does not start a period.+      connect bip94Net (hs ++ [b10]) (mineOn bip94Net b10 (t + 10799) 0x207fffff)+        `shouldBe` Right 11+      connect noBip94Net hs (mineOn noBip94Net par (t + 11399) 0x207fffff)+        `shouldBe` Right 10+    it "allows the first block of a period 600 seconds before its parent" $ do+      validTimeWarp bip94Net 10 10000 9400 `shouldBe` True+      validTimeWarp bip94Net 10 10000 9399 `shouldBe` False+      validTimeWarp bip94Net 11 10000 0 `shouldBe` True+      validTimeWarp noBip94Net 10 10000 0 `shouldBe` True++medianTimeSpec :: Spec+medianTimeSpec = describe "median time past" $ do+  it "takes the middle of an odd count" $+    medianTime [5, 1, 3] `shouldBe` 3+  it "takes the upper middle of an even count" $+    medianTime [4, 1, 3, 2] `shouldBe` 3+  it "takes the only element" $+    medianTime [7] `shouldBe` 7+  it "is the genesis timestamp for genesis" $+    withChain btcRegTest (getBestBlockHeader >>= mtp) `shouldBe` 1296688602+  it "covers the block and its ten ancestors" $+    -- blocks 10 to 20 are 600 seconds apart, the median is block 15+    withChain btcRegTest (storeChain (evenlySpaced btcRegTest 600 20 0x207fffff) >>= mtp . last)+      `shouldBe` 1296688602 + 600 * 15+  it "covers fewer blocks near genesis" $+    -- genesis and blocks 1 to 3: the upper middle of four is block 2+    withChain btcRegTest (storeChain (evenlySpaced btcRegTest 600 3 0x207fffff) >>= mtp . last)+      `shouldBe` 1296688602 + 600 * 2++bitcoinDifficultySpec :: Spec+bitcoinDifficultySpec = describe "bitcoin difficulty" $ do+  let t0 = retargetNet.genesisHeader.timestamp+      mainnetHeader = (btc.genesisHeader :: BlockHeader) {bits = 0x1703a30c, timestamp = 20000000}+  it "has a retarget interval of 2016 blocks" $ do+    diffInterval btc `shouldBe` 2016+    diffInterval retargetNet `shouldBe` 10+  it "keeps the target when the period took the target timespan" $+    -- 1209600 seconds is two weeks+    calcNextWork btc mainnetHeader (20000000 - 1209600) `shouldBe` 0x1703a30c+  it "raises the target at most four times" $+    -- 0x03a30c * 4 = 0x0e8c30+    calcNextWork btc mainnetHeader (20000000 - 10 * 1209600) `shouldBe` 0x170e8c30+  it "lowers the target at most to a quarter" $+    -- 0x03a30c / 4 = 0x00e8c3+    calcNextWork btc mainnetHeader (20000000 - 1) `shouldBe` 0x1700e8c3+  it "treats a period that ends before it starts as a short one" $+    calcNextWork btc mainnetHeader (20000000 + 1) `shouldBe` 0x1700e8c3+  it "does not raise the target above the limit" $+    calcNextWork btc (btc.genesisHeader :: BlockHeader) {timestamp = 20000000} (20000000 - 2 * 1209600) `shouldBe` 0x1d00ffff+  it "keeps the bits when the network does not retarget" $+    calcNextWork btcRegTest mainnetHeader (20000000 - 10 * 1209600) `shouldBe` 0x1703a30c+  it "requires the parent's bits inside a period" $+    requiredOn retargetNet (evenlySpaced retargetNet 600 5 0x1f400000) (t0 + 600 * 6) `shouldBe` 0x1f400000+  it "keeps the target when the first period took the target timespan" $+    -- block 9 is 6000 seconds after genesis+    requiredOn retargetNet (nineBlocks (t0 + 6000)) (t0 + 6600) `shouldBe` 0x1f400000+  it "lowers the target in proportion to a fast period" $+    -- 5400 of 6000 seconds: 0x400000 * 0.9 = 0x399999.9+    requiredOn retargetNet (nineBlocks (t0 + 5400)) (t0 + 6000) `shouldBe` 0x1f399999+  it "doubles the target for a period that took twice as long" $+    -- 2^246 * 2 = 2^247 = 0x80 * 2^240, whose sign bit moves the mantissa down one byte+    requiredOn retargetNet (nineBlocks (t0 + 12000)) (t0 + 12600) `shouldBe` 0x20008000+  it "raises the target at most four times over a period" $+    -- 30000 of 6000 seconds is clamped to 24000: 2^246 * 4 = 2^248+    requiredOn retargetNet (nineBlocks (t0 + 30000)) (t0 + 30600) `shouldBe` 0x20010000+  it "lowers the target at most to a quarter over a period" $+    -- 600 of 6000 seconds is clamped to 1500: 2^246 / 4 = 2^244+    requiredOn retargetNet (nineBlocks (t0 + 600)) (t0 + 1200) `shouldBe` 0x1f100000+  it "treats a period that ends before it starts as a short one" $+    requiredOn retargetNet (nineBlocks (t0 - 100)) (t0 + 600) `shouldBe` 0x1f100000+  it "does not raise the target above the limit over a period" $+    requiredOn retargetNet (map (\(t, _) -> (t, 0x207fffff)) (nineBlocks (t0 + 12000))) (t0 + 12600) `shouldBe` 0x207fffff+  describe "min-difficulty rule" $ do+    let minNet = (retargetNet :: Network) {minDiffBlocks = True}+        -- blocks 1 to 5 at the normal target, block 6 at the minimum after a long gap+        withMinDiffBlock = evenlySpaced minNet 600 5 0x1f400000 ++ [(t0 + 600 * 5 + 1300, 0x207fffff)]+    it "allows the minimum difficulty after twice the target spacing" $+      requiredOn minNet (evenlySpaced minNet 600 5 0x1f400000) (t0 + 600 * 5 + 1201) `shouldBe` 0x207fffff+    it "requires the normal difficulty at exactly twice the target spacing" $+      requiredOn minNet (evenlySpaced minNet 600 5 0x1f400000) (t0 + 600 * 5 + 1200) `shouldBe` 0x1f400000+    it "requires the last normal difficulty after a minimum-difficulty block" $+      requiredOn minNet withMinDiffBlock (t0 + 600 * 5 + 1300 + 600) `shouldBe` 0x1f400000+    it "does not apply on networks without the rule" $+      requiredOn retargetNet (evenlySpaced retargetNet 600 5 0x1f400000) (t0 + 600 * 5 + 1201) `shouldBe` 0x1f400000+    it "finds the last block without minimum difficulty" $+      let heights = withChain minNet $ do+            nodes <- storeChain withMinDiffBlock+            forM [nodes !! 5, nodes !! 4, nodes !! 0] $ fmap (.height) . lastNoMinDiff minNet+       in heights `shouldBe` [5, 5, 1]+    it "stops at the first block of a period" $+      -- block 10 starts a period, so it is never a special block+      withChain minNet (storeChain (evenlySpaced minNet 600 10 0x207fffff) >>= fmap (.height) . lastNoMinDiff minNet . last)+        `shouldBe` 10+    it "stops at genesis" $+      withChain minNet (getBestBlockHeader >>= fmap (.height) . lastNoMinDiff minNet) `shouldBe` 0+  describe "BIP94 retarget" $ do+    let bip94MinNet = (retargetNet :: Network) {bip94 = True, minDiffBlocks = True}+        noBip94MinNet = (retargetNet :: Network) {minDiffBlocks = True}+        -- blocks 1 to 9 take exactly one target timespan; blocks 10 to 19 take+        -- four, and block 19 is a minimum-difficulty block+        twoPeriods =+          [(t0 + 600 * i, 0x1f400000) | i <- [1 .. 8]]+            ++ [(t0 + 6000, 0x1f400000)]+            ++ [(t0 + 6600 + 600 * i, 0x1f400000) | i <- [0 .. 8]]+            ++ [(t0 + 30600, 0x207fffff)]+    it "bases the retarget on the first block of the period" $+      -- four times the target of block 10: 2^246 * 4 = 2^248+      requiredOn bip94MinNet twoPeriods (t0 + 31200) `shouldBe` 0x20010000+    it "bases the retarget on the last block without BIP94" $+      -- four times the minimum difficulty is clamped to the limit+      requiredOn noBip94MinNet twoPeriods (t0 + 31200) `shouldBe` 0x207fffff+  where+    nineBlocks lastTime = evenlySpaced retargetNet 600 8 0x1f400000 ++ [(lastTime, 0x1f400000)]+    requiredOn net specs time = withChain net (storeChain specs >> requiredAfter net time)++cashDifficultySpec :: Spec+cashDifficultySpec = describe "bitcoin cash difficulty" $ do+  let t0 = cashNet.genesisHeader.timestamp+  describe "regtest" $+    it "never retargets" $+      -- a regtest chain at a harder target with an hour-long gap+      withChain bchRegTest (storeChain (evenlySpaced bchRegTest 600 5 0x1f400000) >> requiredAfter bchRegTest (t0 + 3000 + 3600))+        `shouldBe` 0x1f400000+  describe "emergency difficulty adjustment" $ do+    it "keeps the target when the last six blocks took less than twelve hours" $+      requiredOn edaNet (evenlySpaced edaNet 600 20 0x1f400000) (t0 + 600 * 21) `shouldBe` 0x1f400000+    it "keeps the target just below twelve hours" $+      -- 6 * 7199 = 43194 seconds between the two median times+      requiredOn edaNet (evenlySpaced edaNet 7199 20 0x1f400000) (t0 + 7199 * 21) `shouldBe` 0x1f400000+    it "raises the target by a quarter when the last six blocks took twelve hours" $+      -- 0x400000 + 0x100000 = 0x500000+      requiredOn edaNet (evenlySpaced edaNet 7200 20 0x1f400000) (t0 + 7200 * 21) `shouldBe` 0x1f500000+    it "does not raise the target above the limit" $+      requiredOn edaNet (evenlySpaced edaNet 7200 20 0x207fffff) (t0 + 7200 * 21) `shouldBe` 0x207fffff+    it "retargets normally at the end of a period" $+      -- 5400 of 6000 seconds: 0x400000 * 0.9 = 0x399999.9+      let shortNet = (edaNet :: Network) {targetTimespan = 10 * 600}+       in requiredOn shortNet (evenlySpaced shortNet 600 9 0x1f400000) (t0 + 6000) `shouldBe` 0x1f399999+    it "ignores the gap to the parent on mainnet" $+      requiredOn edaNet (evenlySpaced edaNet 600 20 0x1f400000) (t0 + 600 * 20 + 1201) `shouldBe` 0x1f400000+    describe "on test networks" $ do+      let testNet = (edaNet :: Network) {minDiffBlocks = True}+          withMinDiffBlock = evenlySpaced testNet 600 5 0x1f400000 ++ [(t0 + 600 * 5 + 1300, 0x207fffff)]+      it "allows the minimum difficulty after twice the target spacing" $+        requiredOn testNet (evenlySpaced testNet 600 20 0x1f400000) (t0 + 600 * 20 + 1201) `shouldBe` 0x207fffff+      it "requires the last normal difficulty after a minimum-difficulty block" $+        requiredOn testNet withMinDiffBlock (t0 + 600 * 5 + 1300 + 600) `shouldBe` 0x1f400000+      it "does not use the twelve-hour rule" $+        -- the last six blocks took twelve hours, the new block follows its parent closely+        requiredOn testNet (evenlySpaced testNet 7200 20 0x1f400000) (t0 + 7200 * 20 + 600) `shouldBe` 0x1f400000+  describe "difficulty adjustment algorithm" $ do+    -- The window runs from the suitable block before block 2056 to the one+    -- before block 2200: 144 blocks of work at 0x1f7fffff, which is 512 each.+    let window spacing = evenlySpaced daaNet spacing 2200 0x1f7fffff+    it "keeps the target when blocks come every ten minutes" $+      -- (2^256 - 512) / 512 = 2^247 - 1+      requiredOn daaNet (window 600) (t0 + 600 * 2201) `shouldBe` 0x1f7fffff+    it "halves the target when blocks come every five minutes" $+      -- (2^256 - 1024) / 1024 = 2^246 - 1+      requiredOn daaNet (window 300) (t0 + 300 * 2201) `shouldBe` 0x1f3fffff+    it "doubles the target when blocks come every twenty minutes" $+      -- (2^256 - 256) / 256 = 2^248 - 1, whose top byte has the sign bit set+      requiredOn daaNet (window 1200) (t0 + 1200 * 2201) `shouldBe` 0x2000ffff+    it "halves the target at most over a window" $+      -- 144 * 150 = 21600 seconds is clamped to 72 * 600 = 43200+      requiredOn daaNet (window 150) (t0 + 150 * 2201) `shouldBe` 0x1f3fffff+    it "doubles the target at most over a window" $+      -- 144 * 2400 = 345600 seconds is clamped to 288 * 600 = 172800+      requiredOn daaNet (window 2400) (t0 + 2400 * 2201) `shouldBe` 0x2000ffff+    it "ignores the gap to the parent on mainnet" $+      requiredOn daaNet (window 600) (t0 + 600 * 2200 + 1201) `shouldBe` 0x1f7fffff+    it "allows the minimum difficulty after twice the target spacing on test networks" $+      let testNet = (daaNet :: Network) {minDiffBlocks = True}+       in requiredOn testNet (window 600) (t0 + 600 * 2200 + 1201) `shouldBe` 0x207fffff+    it "computes the target from the work and time of a window" $ do+      -- 144 * 2^200 work over 144 * 600 seconds is 2^200 work per 600 seconds:+      -- (2^256 - 2^200) / 2^200 = 2^56 - 1+      computeTarget bch (node 0 1000000) (node (144 * 2 ^ (200 :: Int)) (1000000 + 86400)) `shouldBe` 72057594037927935+      -- one second is clamped to 43200: 2^201 work per 600 seconds gives 2^55 - 1+      computeTarget bch (node 0 1000000) (node (144 * 2 ^ (200 :: Int)) 1000001) `shouldBe` 36028797018963967+      -- a window that ends before it starts is clamped the same way+      computeTarget bch (node 0 1000000) (node (144 * 2 ^ (200 :: Int)) 999999) `shouldBe` 36028797018963967+      -- a million seconds is clamped to 172800: 2^199 work per 600 seconds gives 2^57 - 1+      computeTarget bch (node 0 1000000) (node (144 * 2 ^ (200 :: Int)) 2000000) `shouldBe` 144115188075855871+    it "picks the block with the median timestamp as suitable" $ do+      suitableHeight [600, 1200, 1800] `shouldBe` 2+      suitableHeight [600, 9000, 1200] `shouldBe` 3+      suitableHeight [9000, 600, 1200] `shouldBe` 3+    it "picks the same block as Bitcoin Cash Node on equal timestamps" $ do+      suitableHeight [600, 1200, 1200] `shouldBe` 2+      suitableHeight [1200, 600, 1200] `shouldBe` 1+      suitableHeight [1200, 1200, 600] `shouldBe` 2+      suitableHeight [600, 600, 600] `shouldBe` 2+  where+    requiredOn net specs time = withChain net (storeChain specs >> requiredAfter net time)+    node w t = (genesisNode bch :: BlockNode) {work = w, header = (bch.genesisHeader :: BlockHeader) {timestamp = t}}+    suitableHeight offsets = withChain cashNet $ do+      nodes <- storeChain [(cashNet.genesisHeader.timestamp + o, 0x207fffff) | o <- offsets]+      (.height) <$> getSuitableBlock (last nodes)++asertSpec :: Spec+asertSpec = describe "asert" $ do+  let t0 = asertNet.genesisHeader.timestamp+      halfLife = 2 * 24 * 60 * 60+      regtestLimit = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+      -- thirty blocks ten minutes apart; block 15 is the first whose median+      -- time past reaches the activation time+      steady = evenlySpaced asertNet 600 30 0x1f400000+      requiredOn net specs time = withChain net (storeChain specs >> requiredAfter net time)+  it "finds the first block whose median time past reaches the activation time" $+    anchorHeights `shouldBe` [Nothing, Just 15, Just 15, Just 15]+  it "has no anchor without an activation time" $+    withChain cashNet (storeChain steady >>= asertAnchor cashNet . last) `shouldBe` Nothing+  it "keeps the target when the chain is on schedule" $+    requiredOn asertNet (take 15 steady) (t0 + 600 * 16) `shouldBe` 0x1f400000+  it "keeps the target later on the schedule" $+    requiredOn asertNet steady (t0 + 600 * 31) `shouldBe` 0x1f400000+  it "doubles the target when the chain is a half-life behind" $+    -- 2^246 * 2 = 2^247 = 0x80 * 2^240, whose sign bit moves the mantissa down one byte+    requiredOn asertNet (take 15 steady) (t0 + 600 * 16 + halfLife) `shouldBe` 0x20008000+  it "halves the target when the chain is a half-life ahead" $+    requiredOn asertNet (take 15 steady) (t0 + 600 * 16 - halfLife) `shouldBe` 0x1f200000+  it "ignores the gap to the parent on mainnet" $+    requiredOn asertNet steady (t0 + 600 * 31 + halfLife) `shouldBe` 0x20008000+  it "allows the minimum difficulty after twice the target spacing on test networks" $+    let testNet = (asertNet :: Network) {minDiffBlocks = True}+     in requiredOn testNet steady (t0 + 600 * 31 + halfLife) `shouldBe` 0x207fffff+  it "uses the previous algorithm before activation" $+    -- block 14 has not reached the activation time, so block 15 keeps its parent's bits+    requiredOn asertNet (take 14 steady) (t0 + 600 * 15 + halfLife) `shouldBe` 0x1f400000+  it "computes the formula on the anchor" $ do+    -- one block and 1200 seconds after the anchor's parent is on schedule;+    -- the half-life is 172800 seconds+    computeAsertBits regtestLimit 172800 0x1f400000 1200 1 `shouldBe` 0x1f400000+    computeAsertBits regtestLimit 172800 0x1f400000 (1200 + 172800) 1 `shouldBe` 0x20008000+    computeAsertBits regtestLimit 172800 0x1f400000 (1200 - 172800) 1 `shouldBe` 0x1f200000+  it "does not raise the target above the limit" $+    computeAsertBits (2 ^ (246 :: Int)) 172800 0x1f400000 (1200 + 172800) 1 `shouldBe` 0x1f400000+  it "does not lower the target below one" $+    -- a target of one halved ten times+    computeAsertBits regtestLimit 172800 0x01010000 (1200 - 10 * 172800) 1 `shouldBe` 0x01010000+  describe "aserti3-2d test vectors" $+    mapM_+      ( \x ->+          asertTests $+            "test_vectors_aserti3-2d_run" ++ printf "%02d" x ++ ".txt"+      )+      [(1 :: Int) .. 12]+  where+    anchorHeights = withChain asertNet $ do+      nodes <- storeChain (evenlySpaced asertNet 600 30 0x1f400000)+      forM [nodes !! 13, nodes !! 14, nodes !! 19, last nodes] $ \n ->+        fmap (.height) <$> asertAnchor asertNet n++checkpointSpec :: Spec+checkpointSpec = describe "checkpoints and versions" $ do+  it "accepts the checkpoint hash at its height" $+    validCP btc 11111 "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d" `shouldBe` True+  it "rejects another hash at a checkpoint height" $+    validCP btc 11111 genesisHash `shouldBe` False+  it "accepts any hash where there is no checkpoint" $+    validCP btc 11112 genesisHash `shouldBe` True+  it "requires new blocks above the last checkpoint reached" $ do+    -- the checkpoints at or below height 50000 are 11111 and 33333+    afterLastCP btc 50000 33334 `shouldBe` True+    afterLastCP btc 50000 33333 `shouldBe` False+    afterLastCP btc 50000 11112 `shouldBe` False+    afterLastCP btc 10000 1 `shouldBe` True+    afterLastCP btcRegTest 10000 1 `shouldBe` True+  it "checks the BIP34 activation block" $ do+    bip34 btc 227931 "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8" `shouldBe` True+    bip34 btc 227931 genesisHash `shouldBe` False+    bip34 btc 227932 genesisHash `shouldBe` True+    -- regtest pins no hash, so any block passes at its BIP34 height+    bip34 btcRegTest 1 genesisHash `shouldBe` True+    bip34 btcRegTest 5 genesisHash `shouldBe` True+    bip34 bchRegTest 100000000 genesisHash `shouldBe` True+  it "requires block versions by height" $ do+    validVersion btc 227930 1 `shouldBe` True+    validVersion btc 227931 1 `shouldBe` False+    validVersion btc 363724 2 `shouldBe` True+    validVersion btc 363725 2 `shouldBe` False+    validVersion btc 388380 3 `shouldBe` True+    validVersion btc 388381 3 `shouldBe` False+    validVersion btc 388381 4 `shouldBe` True+    validVersion btc 800000 0x20000000 `shouldBe` True+  it "treats a version with the top bit set as negative" $ do+    validVersion btc 800000 0xffffffff `shouldBe` False+    validVersion btc 800000 0x80000004 `shouldBe` False+    validVersion btc 0 0xffffffff `shouldBe` True++merkleSpec :: Spec+merkleSpec = describe "merkle trees" $ do+  prop "builds tree of right width at height 1" testTreeWidth+  prop "builds tree of right width at height 0" testBaseWidth+  prop "builds and extracts partial merkle tree" $+    forAll arbitraryNetwork $ \net ->+      forAll+        (listOf1 ((,) <$> arbitraryTxHash <*> arbitrary))+        (buildExtractTree net)+  it "merkle root test vectors" $ mapM_ runMerkleVector merkleVectors+  it "computes tree heights" $+    map calcTreeHeight [0, 1, 2, 3, 4, 5, 8, 9, 1000] `shouldBe` [0, 0, 1, 2, 2, 3, 3, 4, 10]+  it "computes tree widths" $ do+    map (calcTreeWidth 5) [0, 1, 2, 3] `shouldBe` [5, 3, 2, 1]+    map (calcTreeWidth 4) [0, 1, 2] `shouldBe` [4, 2, 1]+  it "refuses to build a root without transactions" $+    buildMerkleRoot [] `shouldBe` Left "buildMerkleRoot: a block needs at least one transaction"+  it "refuses a mutated tree with duplicate siblings" $ do+    buildMerkleRoot [txA, txA] `shouldBe` Left "buildMerkleRoot: duplicate transaction hashes (mutated tree)"+    buildMerkleRoot [txA, txB, txC, txC] `shouldBe` Left "buildMerkleRoot: duplicate transaction hashes (mutated tree)"+  it "pairs the last odd transaction with itself without counting it as a duplicate" $+    buildMerkleRoot [txA, txB, txC] `shouldSatisfy` either (const False) (const True)+  it "uses the transaction hash as the root of a single transaction" $+    buildMerkleRoot [txA] `shouldBe` Right txA.get+  it "unpacks flag bits from the least significant bit" $ do+    decodeMerkleFlags [0xaa] `shouldBe` [False, True, False, True, False, True, False, True]+    decodeMerkleFlags [0x01, 0x80] `shouldBe` [True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True]+    decodeMerkleFlags [] `shouldBe` []+  it "packs flag bits and pads the last byte" $ do+    encodeMerkleFlags [True, True, False, True, True] `shouldBe` [0x1b]+    encodeMerkleFlags (replicate 8 False ++ [True]) `shouldBe` [0x00, 0x01]+    encodeMerkleFlags [] `shouldBe` []+  it "splits a list in chunks" $+    splitIn 3 [1 .. 7 :: Int] `shouldBe` [[1, 2, 3], [4, 5, 6], [7]]+  it "packs bools in a byte" $ do+    boolsToWord8 [] `shouldBe` 0+    boolsToWord8 [True] `shouldBe` 1+    boolsToWord8 [False, False, False, False, False, False, False, True] `shouldBe` 128+  describe "block 100000" $ do+    let txids =+          map+            displayTxHash+            [ "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",+              "fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4",+              "6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4",+              "e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d"+            ]+        root = (displayTxHash "f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766").get+        rightNode = (displayTxHash "8e30899078ca1813be036a073bbf80b86cdddde1c96e9e9c99e9e3782df4ae49").get+    it "parses the block" $ do+      blk <- readBlock "data/block_100000.hex"+      headerHash blk.header `shouldBe` "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"+      map txHash blk.txs `shouldBe` txids+      blk.header.merkle `shouldBe` root+    it "builds its merkle root" $+      buildMerkleRoot txids `shouldBe` Right root+    it "hashes a pair of nodes" $+      hash2 (txids !! 2).get (txids !! 3).get `shouldBe` rightNode+    it "computes single nodes" $ do+      calcHash 0 2 txids `shouldBe` (txids !! 2).get+      calcHash 1 1 txids `shouldBe` rightNode+      calcHash 2 0 txids `shouldBe` root+    it "builds a partial tree for the second transaction" $+      -- root and left node have a match below (1), the first transaction+      -- does not (0), the second does (1), the right node does not (0)+      buildPartialMerkle (zip txids [False, True, False, False])+        `shouldBe` ([True, True, False, True, False], [(txids !! 0).get, (txids !! 1).get, rightNode])+    it "extracts the second transaction from its partial tree" $+      extractMatches btc [True, True, False, True, False, False, False, False] [(txids !! 0).get, (txids !! 1).get, rightNode] 4+        `shouldBe` Right (root, [txids !! 1])+    it "builds a partial tree with no matches" $+      buildPartialMerkle (zip txids (repeat False)) `shouldBe` ([False], [root])+    it "builds a partial tree with every match" $+      buildPartialMerkle (zip txids (repeat True))+        `shouldBe` ([True, True, True, True, True, True, True], map (.get) txids)+  describe "merkle block" $ do+    -- Bitcoin Core's merkle_block_3_and_serialize test: a block with one+    -- transaction, which matches.+    let bytes = fromJust $ decodeHex "0100000079cda856b143d9db2c1caff01d1aecc8630d30625d10e8b4b8b0000000000000b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f196367291b4d4c86041b8fa45d630100000001b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f19630101"+        merkleBlock = either error id $ runGetS deserialize bytes+    it "parses Bitcoin Core's merkle block" $ do+      headerHash merkleBlock.header `shouldBe` "000000000000dab0130bbcc991d3d7ae6b81aa6f50a798888dfe62337458dc45"+      merkleBlock.txn `shouldBe` 1+      length merkleBlock.hashes `shouldBe` 1+      merkleBlock.flags `shouldBe` [True, False, False, False, False, False, False, False]+    it "serializes back to the same bytes" $+      runPutS (serialize merkleBlock) `shouldBe` bytes+    it "extracts the matching transaction" $+      merkleBlockTxs btc merkleBlock `shouldBe` Right [displayTxHash "63194f18be0af63f2c6bc9dc0f777cbefed3d9415c4af83f3ee3a3d669c00cb5"]+    it "accepts the merkle root" $+      testMerkleRoot btc merkleBlock `shouldBe` True+    it "rejects a changed merkle root" $+      merkleBlockTxs btc (MerkleBlock (merkleBlock.header :: BlockHeader) {merkle = txA.get} merkleBlock.txn merkleBlock.hashes merkleBlock.flags)+        `shouldBe` Left "merkleBlockTxs: Merkle root incorrect"+    it "rejects a changed transaction count" $+      testMerkleRoot btc (merkleBlock :: MerkleBlock) {txn = 2} `shouldBe` False+  describe "malformed partial trees" $ do+    it "rejects a block without transactions" $+      extractMatches btc [True] [txA.get] 0 `shouldBe` Left "extractMatches: number of transactions can not be 0"+    it "rejects too many transactions" $+      -- 1000000 bytes / 60 bytes per transaction is 16666+      extractMatches btc [True] [txA.get] 16667 `shouldBe` Left "extractMatches: number of transactions excessively high"+    it "rejects more hashes than transactions" $+      extractMatches btc [True] [txA.get, txB.get] 1 `shouldBe` Left "extractMatches: More hashes provided than the number of transactions"+    it "rejects fewer flags than hashes" $+      extractMatches btc [True] [txA.get, txB.get] 2 `shouldBe` Left "extractMatches: At least one bit per node and one bit per hash"+    it "rejects a tree that runs out of flags" $+      extractMatches btc [True, True] [txA.get, txB.get] 2 `shouldBe` Left "extractMatches: ran out of flag bits"+    it "rejects a tree that runs out of hashes" $+      extractMatches btc [True, True, True] [txA.get] 2 `shouldBe` Left "extractMatches: ran out of hashes"+    it "rejects identical left and right branches" $+      -- a mutated tree: the four leaves [a, b, c, c] have the root of [a, b, c]+      extractMatches btc [True, True, True, True, True, True, True, False] (map (.get) [txA, txB, txC, txC]) 4+        `shouldBe` Left "extractMatches: identical left and right branches (mutated tree)"+    it "rejects unused hashes" $+      extractMatches btc (False : replicate 7 False) [txA.get, txB.get] 2 `shouldBe` Left "extractMatches: All hashes were not consumed"+    it "rejects unused flags beyond the last byte" $+      extractMatches btc (True : replicate 8 False) [txA.get] 1 `shouldBe` Left "extractMatches: All bits were not consumed"+    it "accepts unused flags within the last byte" $+      extractMatches btc (True : replicate 7 False) [txA.get] 1 `shouldBe` Right (txA.get, [txA])++-- 0 → → 2015 → → → → → → → 4031+--       ↓+--       → → 2035 → → → → → → 4035*+--           ↓+--           → → 2185+splitChain :: Network -> State HeaderMemory ()+splitChain net = do+  start <- go 1 net.genesisHeader 2015+  e 2015 start+  tail1 <- go 2 start.header 2016+  e 4031 tail1+  tail2 <- go 3 start.header 20+  e 2035 tail2+  tail3 <- go 4 tail2.header 2000+  e 4035 tail3+  tail4 <- go 5 tail2.header 150+  e 2185 tail4+  sp1 <- splitPoint tail1 tail3+  unless (sp1 == start) $+    error $+      "Split point wrong between blocks 4031 and 4035: "+        ++ show sp1.height+  sp2 <- splitPoint tail4 tail3+  unless (sp2 == tail2) $+    error $+      "Split point wrong between blocks 2185 and 4035: "+        ++ show sp2.height+  where+    e n bn@BlockNode {} =+      unless (bn.height == n) $+        error $+          "Node height "+            ++ show bn.height+            ++ " of first chunk should be "+            ++ show n+    -- mine and connect n blocks on top of the start header, returning the tip+    go seed start n = do+      let bhs = appendBlocks net seed start n+      bnE <- connectBlocks net myTime bhs+      case bnE of+        Right (tip : _) -> return tip+        Right [] -> error "No blocks were connected"+        Left ex -> error ex++{- Merkle Trees -}++testTreeWidth :: Int -> Property+testTreeWidth i = i /= 0 ==> calcTreeWidth (abs i) (calcTreeHeight $ abs i) == 1++testBaseWidth :: Int -> Property+testBaseWidth i = i /= 0 ==> calcTreeWidth (abs i) 0 == abs i++buildExtractTree :: Network -> [(TxHash, Bool)] -> Bool+buildExtractTree net txs =+  Right r == buildMerkleRoot (map fst txs) && m == map fst (filter snd txs)+  where+    (f, h) = buildPartialMerkle txs+    (r, m) =+      either error id $+        extractMatches net f h (length txs)++testCompact :: Assertion+testCompact = do+  assertEqual "vector 1" 0x05123456 (encodeCompact 0x1234560000)+  assertEqual "vector 2" (0x1234560000, False) (decodeCompact 0x05123456)+  assertEqual "vector 3" 0x0600c0de (encodeCompact 0xc0de000000)+  assertEqual "vector 4" (0xc0de000000, False) (decodeCompact 0x0600c0de)+  assertEqual "vector 5" 0x05c0de00 (encodeCompact (-0x40de000000))+  assertEqual "vector 6" (-0x40de000000, False) (decodeCompact 0x05c0de00)++testCompactBitcoinCore :: Assertion+testCompactBitcoinCore = do+  assertEqual "zero" (0, False) (decodeCompact 0x00000000)+  assertEqual+    "zero (encode · decode)"+    0x00000000+    (encodeCompact . fst $ decodeCompact 0x00000000)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x00123456)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x01003456)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x02000056)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x03000000)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x04000000)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x00923456)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x01803456)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x02800056)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x03800000)+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x04800000)+  assertEqual "vector 1 (decode)" (0x12, False) (decodeCompact 0x01123456)+  assertEqual+    "vector 1 (encode · decode)"+    0x01120000+    (encodeCompact . fst $ decodeCompact 0x01123456)+  assertEqual "0x80 bit set" 0x02008000 (encodeCompact 0x80)+  assertEqual+    "vector 2 (negative) (decode)"+    (-0x7e, False)+    (decodeCompact 0x01fedcba)+  assertEqual+    "vector 2 (negative) (encode · decode)"+    0x01fe0000+    (encodeCompact . fst $ decodeCompact 0x01fedcba)+  assertEqual "vector 3 (decode)" (0x1234, False) (decodeCompact 0x02123456)+  assertEqual+    "vector 3 (encode · decode)"+    0x02123400+    (encodeCompact . fst $ decodeCompact 0x02123456)+  assertEqual "vector 4 (decode)" (0x123456, False) (decodeCompact 0x03123456)+  assertEqual+    "vector 4 (encode · decode)"+    0x03123456+    (encodeCompact . fst $ decodeCompact 0x03123456)+  assertEqual+    "vector 5 (decode)"+    (0x12345600, False)+    (decodeCompact 0x04123456)+  assertEqual+    "vector 5 (encode · decode)"+    0x04123456+    (encodeCompact . fst $ decodeCompact 0x04123456)+  assertEqual+    "vector 6 (decode)"+    (-0x12345600, False)+    (decodeCompact 0x04923456)+  assertEqual+    "vector 6 (encode · decode)"+    0x04923456+    (encodeCompact . fst $ decodeCompact 0x04923456)+  assertEqual+    "vector 7 (decode)"+    (0x92340000, False)+    (decodeCompact 0x05009234)+  assertEqual+    "vector 7 (encode · decode)"+    0x05009234+    (encodeCompact . fst $ decodeCompact 0x05009234)+  assertEqual+    "vector 8 (decode)"+    ( 0x1234560000000000000000000000000000000000000000000000000000000000,+      False+    )+    (decodeCompact 0x20123456)+  assertEqual+    "vector 8 (encode · decode)"+    0x20123456+    (encodeCompact . fst $ decodeCompact 0x20123456)+  assertBool "vector 9 (decode) (overflow)" (snd $ decodeCompact 0xff123456)+  assertBool+    "vector 9 (decode) (positive)"+    ((> 0) . fst $ decodeCompact 0xff123456)++runMerkleVector :: (Text, [Text]) -> Assertion+runMerkleVector (r, hs) =+  assertEqual "merkle vector" (Right (f r).get) (buildMerkleRoot (map f hs))+  where+    f = fromJust . hexToTxHash++merkleVectors :: [(Text, [Text])]+merkleVectors =+  -- Block 000000000000cd7e8cf6510303dde76121a1a791c15dba0be4be7022b07cf9e1+  [ ( "fb6698ac95b754256c5e71b4fbe07638cb6ca83ee67f44e181b91727f09f4b1f",+      [ "dd96fdcfaec994bf583af650ff6022980ee0ba1686d84d0a3a2d24eabf34bc52",+        "1bc216f786a564378710ae589916fc8e092ddfb9f24fe6c47b733550d476d5d9",+        "a1db0b0194426064b067899ff2d975fb277fd52dbb1a38370800c76dd6503d41",+        "d69f7fb0e668fbd437d1bf5211cc34d7eb8746f50cfddf705fe10bc2f8f7035f",+        "5b4057cd80be7df5ed2ac42b776897ed3c26e3a01e4072075b8129c587094ef6",+        "ed6dabcfba0ef43c50d89a8a0e4b236b1bc6585d4c3bbf49728b55f44312d6bc",+        "056aaa9a3c635909c794e9b0acc7dccb0456c59a84c6b08417335bee4515e3d3",+        "05bae5f1d1c874171692e1fc06f664e63eb143d3f096601ef938e4a9012eee66",+        "b5e48e94e3f2fba197b3f591e01f47e185d7834d669529d44078e41c671aab0f",+        "3b56aeadfc0c5484fd507bc89f13f2e5f61c42e0a4ae9062eda9a9aeef7db6a4",+        "2affa187e1ebb94a2a86578b9f64951e854ff3d346fef259acfb6d0f5212e0d3"+      ]+    ),+    -- Block 00000000000007cc4b6f07bfed72bccc1ed8dd031a93969a4c22211f784457d4++    ( "886fea311d2dc64c315519f2d647e43998d780d2170f77e53dc0d85bf2ee680c",+      [ "c9c9e5211512629fd111cc071d745b8c79bf486b4ea95489eb5de08b5d786b8e",+        "20beb0ee30dfd323ade790ce9a46ae7a174f9ea44ce22a17c4d4eb23b7016f51",+        "d4cb7dd741e78a8f57e12f6c8ddb0361ff2a5bf9365bd7d7df761060847daf9a",+        "ddbfa6fdd29d4b47aeaadf82a4bf0a93d58cd7d8401fabf860a1ae8eeb51f42e",+        "9d82bafe44abee248b968c86f165051c8413482c232659795335c52922dab471",+        "86035372d31b53efd848cea7231aa9738c209aff64d3c59b1619341afb5b6ba3",+        "11e7a7393d9658813dfaebc04fa6d4b73bac8d641bffa7067da879523d43d030",+        "2f676b9aa5bc0ebf3395032c84c466e40cac29f80434cd1138e31c2d0fcc5c13",+        "37567d559fbfae07fda9a90de0ce30b202128bc8ebdfef5ad2b53e865a3478c2",+        "0b8e6c1200c454361e94e261738429e9c9b8dcffd85ec8511bbf5dc7e2e0ada8"+      ]+    ),+    -- Block 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048++    ( "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",+      ["0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"]+    ),+    -- Block 000000000004d160ac1f7b775d7c1823345aeadd5fcb29ca2ad2403bb7babd4c++    ( "aae018650f513fc42d55b2210ec3ceeeb194fb1261d37989de07451fc0cbac5c",+      [ "a4454f22831acd7904a9902c5070a3ee4bf4c2b13bc6b2dc66735dd3c4414028",+        "45297f334278885108dd38a0b689ed95a4373dd3f7e4413e6aebdc2654fb771b"+      ]+    ),+    -- Block 000000000001d1b13a7e86ddb20da178f20d6da5cd037a29c2a15b8b84cc774e++    ( "ca3580505feb87544760ac14a5859659e23be05f765bbed9f86a3c9aad1a5d0c",+      [ "60702384c6e9d34ff03c2b3e726bdc649befe603216815bd0a2974921d0d9549",+        "11f40f58941d2a81a1616a3b84b7dd8b9d07e68750827de488c11a18f54220bb",+        "d78e82527aa8cf16e375010bc666362c0258d3c0da1885a1871121706da8b633"+      ]+    ),+    -- Block 0000000000000630a4e2266a31776e952a19b7c99a6387917d9de9032f608021++    ( "dcce8be0a9a41e7bb726c5b49d957d90b5308e3dc5dce070ccbc8996e265a6c2",+      [ "c0f58ff12cd1023b05f8f7035cc62bf50958ddb216a4e0eb5471deb7ef25fe81",+        "24e5bbf9008641b8fcf3d076fef66c28c695362ba9f6a6042f8275a98414ee92",+        "e8e1f72abad5e34dabc0f6de46a484b17a9af857d1c41de19482fadf6f7f4b27",+        "540e4d34d9fd9e5ec02853054be7ad9260379bc23388489049cca1b0f7cf518a",+        "324444835c5fe0545f98c4240011b75e6ea1bb76f41829e4cfbe7f75b6cee924",+        "e7d31437ac21bceb0c222a82b2723e2b8a7654147e33397679f041537022a4b2",+        "a8b5768d8b33525ee89d546a6a6897f8e42ba9d56a2c5e871a5d2ab40258dc95",+        "7ba712b31bae8d45810a5cda3838c7e7fb9abd6e88bb4b3ee79be9ea2f714bb4",+        "2ae1c4d927b06edaa626b230976ad8062bbae24da9378d1de2409da5ab08a26d",+        "3c417dc8087d6878003624b74431e17fec9ca761389034b1b1e0f32cbfb11f4f",+        "de6de7beae8d8c98c7d46b4409d5460e58e3204d8b4caed256c7471998595909",+        "c7c3c211402b7c4379f7b01fadc67260ee58d11e8d0bcce3d68cb45f3467e99d",+        "77aa2717e727a096d81074bd46ae59462692d20a1acc1a01b2535518ae5aeb53",+        "4859a710bb673aca46208bbd59d1000ae990dafff5f70b56f0853aeeaea3948b",+        "38deca6991988e461b83aa0d49ffef0f304c4b760371682d152eeb8c56a48174",+        "648f4f50dada3574e2dfe2dc68956b01dd97d543859a3540bbe1ef5418d0e494",+        "9cd7be42c2f0cd8bf38738c162cd05108e213ec7958bf2571cb627872963f5c4",+        "6740e0dd8b97e23864af41839fc197238d2f0dbefce9a82c657556be65c465fa",+        "f75c2e4b70db4b0aabc44b77af1ae75d305340fcf6e7b5f806ddcba4aa42b55d",+        "e125c488636749da68e6696b97525a77146c0777c7946927e37afd513d74a4e6",+        "c20526f119aea10880af631eba7f0b60385a22e0b0c402fe8508d41952e58be9",+        "6456c023c7e245f5c57a168633a23f57f4fadb651115f807694a6bed14ae3b55",+        "98b26e364e2888c9f264e4b5e13103c89608609774eb07ce933d8a2a45d19776",+        "2efaa4f167bb65ba5684f8076cd9279fd67fd9c67388c8862809bab5542e637d",+        "ec44eeb84d8d976d77079a822710b4dfdb11a2d9a03d8cc00bab0ae424e84666",+        "410730d9f807d81ac48b8eafac6f1d36642c1c370241b367a35f0bac6ac7c05f",+        "e95a7d0d477fd3db22756a3fd390a50c7bc48dc9e946fea9d24bd0866b3bb0e9",+        "a72fec99d14939216628aaf7a0afc4c017113bcae964e777e6b508864eeaacc4",+        "8548433310fcf75dbbc042121e8318c678e0a017534786dd322a91cebe8d213f"+      ]+    )+  ]++testSubsidy :: Network -> Assertion+testSubsidy net = go (2 * 50 * 100 * 1000 * 1000) 0+  where+    go previous_subsidy halvings = do+      let height = halvings * net.halvingInterval+          subsidy = computeSubsidy net height+      if halvings >= 64+        then subsidy `shouldBe` 0+        else do+          subsidy `shouldBe` (previous_subsidy `div` 2)+          go subsidy (halvings + 1)++data AsertBlock = AsertBlock Int Integer Integer Word32++data AsertVector = AsertVector String Integer Integer Word32 [AsertBlock]++readAsertVector :: FilePath -> IO AsertVector+readAsertVector p = do+  (d : ah : apt : ab : _ : _ : _ : _ : xs) <- lines <$> readFile ("data/" ++ p)+  let desc = drop 16 d+      anchor_height = read (words ah !! 3)+      anchor_parent_time = read (words apt !! 4)+      anchor_nbits = read (words ab !! 3)+      blocks = map (f . words) (init xs)+  return $+    AsertVector+      desc+      anchor_height+      anchor_parent_time+      anchor_nbits+      blocks+  where+    f [i, h, t, g] = AsertBlock (read i) (read h) (read t) (read g)+    f _ = undefined++asertTests :: FilePath -> SpecWith ()+asertTests file = do+  v@(AsertVector d _ _ _ _) <- runIO $ readAsertVector file+  it d $ testAsertBits v++-- | The vectors use the mainnet proof-of-work limit and a two-day half-life.+testAsertBits :: AsertVector -> Assertion+testAsertBits (AsertVector _ anchor_height anchor_parent_time anchor_bits blocks) =+  forM_ blocks $ \(AsertBlock _ h t g) ->+    computeAsertBits+      0x00000000ffff0000000000000000000000000000000000000000000000000000       (2 * 24 * 60 * 60)       anchor_bits       (t - anchor_parent_time)
test/Haskoin/Crypto/HashSpec.hs view
@@ -5,7 +5,10 @@  module Haskoin.Crypto.HashSpec (spec) where +import Control.Exception (evaluate)+import Crypto.Hash (hashFinalize, hashUpdate) import Data.Bits+import Data.ByteArray qualified as BA import Data.ByteString (ByteString) import Data.ByteString.Builder import Data.ByteString.Char8 qualified as Char8@@ -28,6 +31,7 @@ import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck+import Text.Read (readMaybe)  identityTests :: IdentityTests identityTests =@@ -77,6 +81,59 @@         mapM_ (testHMACVector hmac256 (.get)) hmacSha256Vectors       it "Passes HMAC_SHA512 test vectors" $         mapM_ (testHMACVector hmac512 (.get)) hmacSha512Vectors+    describe "Composite hashes" $ do+      it "double SHA-256 hashes the SHA-256 of the input again" $ do+        -- sha256(sha256("")) and sha256(sha256("abc"))+        doubleSHA256 ("" :: ByteString)+          `shouldBe` "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"+        doubleSHA256 ("abc" :: ByteString)+          `shouldBe` "4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358"+      it "address hash is the RIPEMD-160 of the SHA-256 of the input" $ do+        addressHash ("" :: ByteString)+          `shouldBe` "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"+        addressHash ("abc" :: ByteString)+          `shouldBe` "bb1be98c142444d7a56aa3981c3942a978e4dc33"+      it "32-bit checksum is the first four bytes of the double SHA-256" $+        -- double SHA-256 of "" starts with 5d f6 e0 e2+        (checkSum32 ("" :: ByteString)).get `shouldBe` 0x5df6e0e2+      it "tagged hash is SHA-256 of the hashed tag twice and the message" $ do+        -- sha256(sha256("TapLeaf") || sha256("TapLeaf") || msg), as in BIP340+        taggedHex "TapLeaf" ""+          `shouldBe` "5212c288a377d1f8164962a5a13429f9ba6a7b84e59776a52c6637df2106facb"+        taggedHex "TapLeaf" "abc"+          `shouldBe` "83a56308a9c56f467e8df293da5ae5fdbc85b871952a83c4bf0575ee948ec230"+      it "split512 gives the two halves of a 512-bit hash" $+        -- SHA-512 of "" is cf83e135…d36ce9ce followed by 47d0d13c…f927da3e+        split512 (sha512 ("" :: ByteString))+          `shouldBe` ( "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce",+                       "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"+                     )+      it "join512 puts the two halves back together" $+        join512+          ( "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce",+            "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"+          )+          `shouldBe` "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\+                     \47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"+    describe "Hash text forms" $ do+      it "reads a hash from its hex string" $+        readMaybe "\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\""+          `shouldBe` Just ("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" :: Hash256)+      it "refuses to read a hash of the wrong length" $ do+        readMaybe "\"abcd\"" `shouldBe` (Nothing :: Maybe Hash160)+        readMaybe "\"abcd\"" `shouldBe` (Nothing :: Maybe Hash256)+        readMaybe "\"abcd\"" `shouldBe` (Nothing :: Maybe Hash512)+      it "refuses to read a hash that is not hex" $+        readMaybe "\"zz\"" `shouldBe` (Nothing :: Maybe Hash256)+      it "refuses a hash literal of the wrong length" $ do+        evaluate ("abcd" :: Hash160) `shouldThrow` anyErrorCall+        evaluate ("abcd" :: Hash256) `shouldThrow` anyErrorCall+        evaluate ("abcd" :: Hash512) `shouldThrow` anyErrorCall++-- | Hex of the BIP340 tagged hash of a message.+taggedHex :: ByteString -> ByteString -> Text+taggedHex tag msg =+  encodeHex (BA.convert (hashFinalize (hashUpdate (initTaggedHash tag) msg)))  joinSplit512 :: Hash256 -> Hash256 -> Bool joinSplit512 a b = split512 (join512 (a, b)) == (a, b)
test/Haskoin/Crypto/Keys/ExtendedSpec.hs view
@@ -6,9 +6,11 @@  module Haskoin.Crypto.Keys.ExtendedSpec (spec) where +import Control.Exception (evaluate) import Control.Monad (forM_) import Data.Aeson as A import Data.Bits ((.&.))+import Data.ByteString qualified as B import Data.ByteString.Lazy.Char8 qualified as B8 import Data.Bytes.Get import Data.Bytes.Put@@ -16,26 +18,29 @@ import Data.Default (def) import Data.Either (isLeft) import Data.Maybe (fromJust, isJust, isNothing)+import Data.Ord (comparing) import Data.String (fromString) import Data.String.Conversions (cs) import Data.Text (Text)+import Data.Text qualified as T import Data.Word (Word32) import Haskoin.Address import Haskoin.Crypto import Haskoin.Network.Constants+import Haskoin.Script import Haskoin.Util import Haskoin.Util.Arbitrary import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck hiding ((.&.))+import Text.Read (readMaybe)  identityTests :: Ctx -> IdentityTests identityTests ctx =   def     { readTests =-        [ ReadBox arbitraryXPrvKey,-          ReadBox (arbitraryXPubKey ctx),+        [ ReadBox (arbitraryXPubKey ctx),           ReadBox arbitraryDerivPath,           ReadBox arbitraryHardPath,           ReadBox arbitrarySoftPath,@@ -80,6 +85,257 @@   describe "bip32 subkey derivation vector 1" $ vectorSpec ctx m1 vector1   describe "bip32 subkey derivation vector 2" $ vectorSpec ctx m2 vector2   describe "bip32 subkey derivation vector 3" $ vectorSpec ctx m3 vector3+  describe "bip32 test vector 4: private keys with leading zeros" $+    forM_ vector4 $ \(path, xpubText, xprvText) ->+      it ("chain " <> path) $ do+        let m = makeXPrvKey (fromJust (decodeHex m4))+            k = derivePath ctx ((.get) (fromJust (parsePath path))) m+        xPrvExport btc k `shouldBe` xprvText+        xPubExport btc ctx (deriveXPubKey ctx k) `shouldBe` xpubText+  describe "bip32 test vector 5: invalid extended keys" $+    forM_ vector5 $ \(key, reason) ->+      it ("refuses " <> reason) $ do+        xPrvImport btc key `shouldBe` Nothing+        xPubImport btc ctx key `shouldBe` Nothing+  describe "Extended key import" $ do+    it "refuses an xpub with trailing bytes" $+      -- the 78 bytes of the vector 1 master xpub followed by de ad be ef+      xPubImport btc ctx "7JJikZQ2NUXjSAnAF2v9rxutS1j84JbPJspH9roCjFfNvxFfhmgTczN53frJ4RaYoKmwYCj5r1qRsGbLz2fiUE4Aw7LqCR9NYxR72w6AThhWJsB43ZrGA"+        `shouldBe` Nothing+    it "refuses an xprv with trailing bytes" $+      xPrvImport btc "7JJRGiiVuW9DCAkyM2PaXqVx51tHCM3s2Wbq9SnmstZD4r8Ryf4i7Dbe2YQyz9mUF9oZEcdkyzU96dsBwJz3Uvu6PzVTC5k53mBrVHWxZMPdSBtmunEjn"+        `shouldBe` Nothing+    it "refuses an xpub carrying an uncompressed public key" $+      -- 110 bytes: the vector 1 master xpub with its key in the 65-byte form+      xPubImport btc ctx "HarqLWPftSVzTwufEPMMMdZS33daVnF1pMQCFXEQoWwsd7tK1oCwB34reM1xTwhKvY11Pfgite2BcpvEDscYTtuZYL3pZAwQzAoZRLA1xCjK2F28jNBa3muZtLmsqfWHzwSGk2RRAd1e52SvdFecN97hmfA"+        `shouldBe` Nothing+    it "refuses a string longer than any extended key" $ do+      xPrvImport btc (T.replicate 113 "z") `shouldBe` Nothing+      xPubImport btc ctx (T.replicate 113 "z") `shouldBe` Nothing+    it "refuses an extended key of another network" $ do+      xPrvImport btcTest xprv1Text `shouldBe` Nothing+      xPrvImport btc tprv1Text `shouldBe` Nothing+      xPubImport btcTest ctx xpub1Text `shouldBe` Nothing+      xPubImport btc ctx tpub1Text `shouldBe` Nothing+    it "imports the testnet form of a key on testnet" $ do+      fmap (.key) (xPrvImport btcTest tprv1Text) `shouldBe` Just (xprv1 ctx).key+      fmap (.key) (xPubImport btcTest ctx tpub1Text) `shouldBe` Just (xpub1 ctx).key+    it "refuses an xpub given to the private key importer, and the reverse" $ do+      xPrvImport btc xpub1Text `shouldBe` Nothing+      xPubImport btc ctx xprv1Text `shouldBe` Nothing+    it "fails the JSON parse of an invalid extended key" $+      -- BIP32 test vector 5: private key 0+      (unmarshalJSON btc "\"xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx\"" :: Maybe XPrvKey)+        `shouldBe` Nothing+  describe "Master key" $+    it "has depth 0, no parent and index 0" $ do+      let m = makeXPrvKey (fromJust (decodeHex m1))+      m.depth `shouldBe` 0+      m.parent `shouldBe` "00000000"+      m.index `shouldBe` 0+  describe "Showing an extended private key" $+    -- The master key of vector 1 has fingerprint 3442193e; its hardened+    -- child 7 has index 2^31 + 7 = 2147483655+    it "shows the depth, parent and index but not the chain code or the key" $ do+      let m = makeXPrvKey (fromJust (decodeHex m1))+      show m `shouldBe` "XPrvKey {depth = 0, parent = \"00000000\", index = 0, chain = <redacted>, key = <redacted>}"+      show (hardSubKey ctx m 7) `shouldBe` "XPrvKey {depth = 1, parent = \"3442193e\", index = 2147483655, chain = <redacted>, key = <redacted>}"+      show (Just m) `shouldBe` "Just (XPrvKey {depth = 0, parent = \"00000000\", index = 0, chain = <redacted>, key = <redacted>})"+  describe "Derivation depth" $ do+    let m = makeXPrvKey (fromJust (decodeHex m1))+        atDepth255 = XPrvKey 255 m.parent m.index m.chain m.key+        tooDeep = DerivationException "Maximum derivation depth exceeded"+        hardIndex = DerivationException "Invalid child derivation index"+    it "a child is one level deeper than its parent" $ do+      (prvSubKey ctx m 0).depth `shouldBe` 1+      (hardSubKey ctx m 0).depth `shouldBe` 1+      (pubSubKey ctx (deriveXPubKey ctx m) 0).depth `shouldBe` 1+      (derivePath ctx (Deriv :| 0 :/ 1 :/ 2 :: DerivPath) m).depth `shouldBe` 3+    it "refuses to derive a child of a key at depth 255" $ do+      evaluate (prvSubKey ctx atDepth255 0) `shouldThrow` (== tooDeep)+      evaluate (hardSubKey ctx atDepth255 0) `shouldThrow` (== tooDeep)+      evaluate (pubSubKey ctx (deriveXPubKey ctx atDepth255) 0) `shouldThrow` (== tooDeep)+    it "refuses a child index with the hard bit set with a DerivationException" $ do+      evaluate (prvSubKey ctx m 0x80000000) `shouldThrow` (== hardIndex)+      evaluate (hardSubKey ctx m 0x80000000) `shouldThrow` (== hardIndex)+      evaluate (pubSubKey ctx (deriveXPubKey ctx m) 0x80000000) `shouldThrow` (== hardIndex)+  describe "Child index" $ do+    let m = makeXPrvKey (fromJust (decodeHex m1))+    it "a hardened child stores its index with the hard bit set" $ do+      let k = hardSubKey ctx m 7+      k.index `shouldBe` 0x80000007+      xPrvChild k `shouldBe` 7+      xPrvIsHard k `shouldBe` True+      xPubChild (deriveXPubKey ctx k) `shouldBe` 7+      xPubIsHard (deriveXPubKey ctx k) `shouldBe` True+    it "a soft child stores its index as given" $ do+      let k = prvSubKey ctx m 7+      k.index `shouldBe` 7+      xPrvChild k `shouldBe` 7+      xPrvIsHard k `shouldBe` False+      xPubChild (deriveXPubKey ctx k) `shouldBe` 7+      xPubIsHard (deriveXPubKey ctx k) `shouldBe` False+  describe "Addresses of an extended public key" $ do+    -- BIP32 vector 1 master public key 0339a360…85c2 has HASH160 3442193e…f811+    it "pay to public key hash" $+      addrToText btc (xPubAddr ctx (xpub1 ctx)) `shouldBe` Just "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"+    it "pay to witness public key hash" $+      addrToText btc (xPubWitnessAddr ctx (xpub1 ctx)) `shouldBe` Just "bc1qx3ppj0smkuy3d6g525sh9n2w9k7fm7q3x30rtg"+    it "pay to script hash wrapping a witness public key hash" $+      addrToText btc (xPubCompatWitnessAddr ctx (xpub1 ctx)) `shouldBe` Just "3PpgpssV7mcAGpZRWiCWhodUTnjpoSZg7a"+  describe "Address derivation helpers" $ do+    -- BIP32 vector 2: child 0 of the master key has public key 02fc9e5a…12ea+    it "deriveAddr gives the address and the public key of the child" $ do+      let (addr, key) = deriveAddr ctx (xpub2 ctx) 0+      addrToText btc addr `shouldBe` Just "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ"+      encodeHex (exportPubKey ctx True key)+        `shouldBe` "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"+    it "deriveWitnessAddr gives the witness address of the child" $+      addrToText btc (fst (deriveWitnessAddr ctx (xpub2 ctx) 0))+        `shouldBe` Just "bc1qtfsllr4h4t9rqyxmjl4a5asjzcgt0qykp3q3we"+    it "deriveCompatWitnessAddr gives the wrapped witness address of the child" $+      addrToText btc (fst (deriveCompatWitnessAddr ctx (xpub2 ctx) 0))+        `shouldBe` Just "39cFMGtVaEw6AfksMcaLpu8frZxmqngh8c"+    it "deriveAddrs lists children from the start index and wraps around" $ do+      map lst3 (take 3 (deriveAddrs ctx (xpub2 ctx) 0x7ffffffe))+        `shouldBe` [0x7ffffffe, 0x7fffffff, 0]+      map addressAndIndex (take 1 (deriveAddrs ctx (xpub2 ctx) 5))+        `shouldBe` [(Just "16KmCWXw8kPgr9nnvETXoqzmsYcvscXfGL", 5)]+    it "deriveWitnessAddrs lists witness addresses with their index" $+      map addressAndIndex (take 1 (deriveWitnessAddrs ctx (xpub2 ctx) 5))+        `shouldBe` [(Just "bc1q8fjs9hakl9489q0fem0gya4m8yqr4gl32dhjr9", 5)]+    it "deriveCompatWitnessAddrs lists wrapped witness addresses with their index" $+      map addressAndIndex (take 1 (deriveCompatWitnessAddrs ctx (xpub2 ctx) 5))+        `shouldBe` [(Just "3FzrsDTC8KiEsBeBTrhDnJoJYvkDpWaWR6", 5)]+    it "prvSubKeys, pubSubKeys and hardSubKeys list children with their index" $ do+      let m = makeXPrvKey (fromJust (decodeHex m1))+      map snd (take 3 (prvSubKeys ctx m 0x7ffffffe)) `shouldBe` [0x7ffffffe, 0x7fffffff, 0]+      map snd (take 2 (hardSubKeys ctx m 0)) `shouldBe` [0, 1]+      map ((.index) . fst) (take 1 (hardSubKeys ctx m 1)) `shouldBe` [0x80000001]+      map (\(k, i) -> (addrToText btc (xPubAddr ctx k), i)) (take 1 (pubSubKeys ctx (xpub2 ctx) 0))+        `shouldBe` [(Just "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ", 0)]+    it "cycleIndex runs through the soft indices from the start and wraps around" $ do+      take 3 (cycleIndex 0x7ffffffe) `shouldBe` [0x7ffffffe, 0x7fffffff, 0]+      take 2 (cycleIndex 0) `shouldBe` [0, 1]+      evaluate (cycleIndex 0x80000000) `shouldThrow` (== DerivationException "cycleIndex: invalid index 2147483648")+  describe "Multisig derivation" $ do+    -- 2-of-2 of child 0 of vectors 1 and 2; sorted keys 027c4b09… then 02fc9e5a…+    it "deriveMSAddr gives the P2SH address of the sorted multisig script" $ do+      let (addr, rdm) = deriveMSAddr ctx [xpub2 ctx, xpub1 ctx] 2 0+      addrToText btc addr `shouldBe` Just "3ARSZ47cqQBb9y6QhTk6UoWHMwpruUryZp"+      encodeHex (marshal ctx rdm)+        `shouldBe` "5221027c4b09ffb985c298afe7e5813266cbfcb7780b480ac294b0b43dc21f2be3d13c\+                   \2102fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea52ae"+    it "deriveMSAddrs lists multisig addresses with their index" $ do+      map lst3 (take 2 (deriveMSAddrs ctx [xpub1 ctx, xpub2 ctx] 2 0)) `shouldBe` [0, 1]+      -- 2-of-2 of child 1 of vectors 1 and 2+      map addressAndIndex (take 1 (deriveMSAddrs ctx [xpub1 ctx, xpub2 ctx] 2 1))+        `shouldBe` [(Just "3Pp8RkaJc8eNreFuTTTJ92QfXECQMXDEoE", 1)]+  describe "Address derivation below a path" $ do+    -- vector 2: m/0/0 has public key 0205c889…ef8c+    it "derivePathAddr derives the address of a child below a soft path" $ do+      let (addr, key) = derivePathAddr ctx (xpub2 ctx) (Deriv :/ 0) 0+      addrToText btc addr `shouldBe` Just "1NyMg76BQxDvV6vRsQugNS4ED2hpZCJtwK"+      encodeHex (exportPubKey ctx True key)+        `shouldBe` "0205c8897fd0ff5644adba4545a84020cd6aa94d90e1e0a56bb4b8eb7522e3ef8c"+    it "derivePathAddrs lists children below the path with their index" $+      map addressAndIndex (take 1 (derivePathAddrs ctx (xpub2 ctx) (Deriv :/ 0) 0))+        `shouldBe` [(Just "1NyMg76BQxDvV6vRsQugNS4ED2hpZCJtwK", 0)]+    it "derivePathMSAddr derives a multisig address below a soft path" $ do+      -- 2-of-2 of m/1/0 of vectors 1 and 2+      let (addr, _) = derivePathMSAddr ctx [xpub1 ctx, xpub2 ctx] (Deriv :/ 1) 2 0+      addrToText btc addr `shouldBe` Just "39HHTbxsnt5SrM4h2BtUgVRFzGYcavJ8M4"+    it "derivePathMSAddrs lists multisig addresses below the path with their index" $+      map addressAndIndex (take 1 (derivePathMSAddrs ctx [xpub1 ctx, xpub2 ctx] (Deriv :/ 1) 2 0))+        `shouldBe` [(Just "39HHTbxsnt5SrM4h2BtUgVRFzGYcavJ8M4", 0)]+  describe "Path operations" $ do+    it "++/ appends two paths into a mixed path" $ do+      let p = (Deriv :| 44 :| 0 :: HardPath) ++/ (Deriv :/ 0 :/ 5 :: SoftPath)+      p `shouldBe` (Deriv :| 44 :| 0 :/ 0 :/ 5)+      pathToStr p `shouldBe` "/44'/0'/0/5"+    it "toHard keeps a path only when every segment is hardened" $ do+      toHard (Deriv :| 1 :| 2 :: DerivPath) `shouldBe` Just (Deriv :| 1 :| 2)+      toHard (Deriv :| 1 :/ 2 :: DerivPath) `shouldBe` Nothing+    it "toSoft keeps a path only when no segment is hardened" $ do+      toSoft (Deriv :/ 1 :/ 2 :: DerivPath) `shouldBe` Just (Deriv :/ 1 :/ 2)+      toSoft (Deriv :| 1 :/ 2 :: DerivPath) `shouldBe` Nothing+    it "toGeneric keeps every segment" $ do+      toGeneric (Deriv :| 1 :| 2 :: HardPath) `shouldBe` (Deriv :| 1 :| 2)+      toGeneric (Deriv :/ 1 :/ 2 :: SoftPath) `shouldBe` (Deriv :/ 1 :/ 2)+    it "writes hardened segments with an apostrophe" $ do+      pathToStr (Deriv :| 44 :/ 0 :: DerivPath) `shouldBe` "/44'/0"+      show (ParsedPrv (Deriv :| 44 :/ 0)) `shouldBe` "ParsedPath \"m/44'/0\""+      show (ParsedPub (Deriv :| 44 :/ 0)) `shouldBe` "ParsedPath \"M/44'/0\""+    it "pathToList sets the hard bit on hardened segments" $+      pathToList (Deriv :| 1 :/ 2 :: DerivPath) `shouldBe` [0x80000001, 2]+    it "listToPath reads the hard bit back" $+      listToPath [0x80000001, 2] `shouldBe` (Deriv :| 1 :/ 2)+    it "serializes a path as a count and its indices with the hard bit" $+      runPutS (serialize (Deriv :| 1 :/ 2 :: DerivPath))+        `shouldBe` hex "0000000000000002" <> hex "80000001" <> hex "00000002"+    it "binary hard path refuses a soft segment" $+      (runGetS deserialize (hex "0000000000000001" <> hex "00000001") :: Either String HardPath)+        `shouldSatisfy` isLeft+    it "binary soft path refuses a hardened segment" $+      (runGetS deserialize (hex "0000000000000001" <> hex "80000001") :: Either String SoftPath)+        `shouldSatisfy` isLeft+    it "sorts a shorter path first, then by segment, soft before hard" $ do+      (Deriv :/ 9 :: DerivPath) < (Deriv :/ 0 :/ 1) `shouldBe` True+      (Deriv :/ 5 :: DerivPath) < (Deriv :| 0) `shouldBe` True+      (Deriv :/ 1 :: DerivPath) < (Deriv :/ 2) `shouldBe` True+    prop "ordering agrees with the shortlex order of the index lists" $+      forAll arbitraryDerivPath $ \a ->+        forAll arbitraryDerivPath $ \b ->+          compare a b == comparing (\p -> (length (pathToList p), pathToList p)) a b+  describe "Path parsing" $ do+    it "accepts the apostrophe, h and H as hardened markers" $ do+      parsePath "m/44h/0H/1'" `shouldBe` Just (ParsedPrv (Deriv :| 44 :| 0 :| 1))+      parseHard "m/44h/0H" `shouldBe` Just (Deriv :| 44 :| 0)+      (fromString "m/44h/0H/1" :: DerivPath) `shouldBe` (Deriv :| 44 :| 0 :/ 1)+    it "accepts the largest index, 2^31 - 1" $+      parsePath "m/2147483647'" `shouldBe` Just (ParsedPrv (Deriv :| 2147483647))+    it "refuses an index that does not fit in 31 bits instead of wrapping it" $ do+      parsePath "m/2147483648" `shouldBe` Nothing+      parsePath "m/4294967296" `shouldBe` Nothing -- 2^32, would wrap to 0+      parsePath "m/4294967297'" `shouldBe` Nothing -- 2^32 + 1, would wrap to 1+      parsePath "m/-4294967295" `shouldBe` Nothing -- would wrap to 1+      parsePath "m/18446744073709551617" `shouldBe` Nothing -- 2^64 + 1+    it "refuses anything but decimal digits in an index" $ do+      parsePath "m/0x10" `shouldBe` Nothing+      parsePath "m/(7)" `shouldBe` Nothing+      parsePath "m/+1" `shouldBe` Nothing+      parsePath "m/1e3" `shouldBe` Nothing+      parsePath "m/ 1" `shouldBe` Nothing+      parsePath "m/1 " `shouldBe` Nothing+      parsePath "m/1''" `shouldBe` Nothing+      parsePath "m/'" `shouldBe` Nothing+    it "refuses empty segments" $ do+      parsePath "m/" `shouldBe` Nothing+      parsePath "m//1" `shouldBe` Nothing+      parsePath "m/1/" `shouldBe` Nothing+    it "fails the JSON parse of an out-of-range index" $+      (A.decode "[\"m/4294967296\"]" :: Maybe [DerivPath]) `shouldBe` Nothing+    it "refuses to read a path index that does not fit in 31 bits" $+      readMaybe "Bip32SoftIndex 4294967296" `shouldBe` (Nothing :: Maybe Bip32PathIndex)+  describe "Fingerprint text" $ do+    -- the identifier of the vector 1 master key starts with 3442193e+    it "writes eight hex digits" $+      fingerprintToText (xPubFP ctx (xpub1 ctx)) `shouldBe` "3442193e"+    it "reads eight hex digits" $+      textToFingerprint "3442193e" `shouldBe` Right (xPubFP ctx (xpub1 ctx))+    it "refuses more or fewer than four bytes" $ do+      textToFingerprint "0011223344" `shouldSatisfy` isLeft+      textToFingerprint "001122" `shouldSatisfy` isLeft+      textToFingerprint "" `shouldSatisfy` isLeft+    it "refuses text that is not hex" $+      textToFingerprint "zzzzzzzz" `shouldSatisfy` isLeft+    it "parses from JSON and refuses the wrong length" $ do+      A.decode "\"3442193e\"" `shouldBe` Just (xPubFP ctx (xpub1 ctx))+      (A.decode "\"0011223344\"" :: Maybe Fingerprint) `shouldBe` Nothing+    it "refuses to read the wrong length" $+      readMaybe "\"0011223344\"" `shouldBe` (Nothing :: Maybe Fingerprint)+    it "refuses a literal of the wrong length" $+      evaluate ("0011223344" :: Fingerprint) `shouldThrow` anyErrorCall   describe "bip32 subkey derivation using string path" $ do     it "either derivations" $ testApplyPath ctx     it "either derivations" $ testBadApplyPath ctx@@ -110,7 +366,8 @@   describe "Extended Keys" $ do     let net = btc     prop "computes pubkey of a subkey is subkey of the pubkey" $-      forAll arbitraryXPrvKey $+      -- a key at depth 255 cannot have children+      forAll (arbitraryXPrvKey `suchThat` ((< 255) . (.depth))) $         pubKeyOfSubKeyIsSubKeyOfPubKey ctx     prop "exports and imports extended private key" $       forAll arbitraryXPrvKey $ \k ->@@ -119,6 +376,29 @@       forAll (arbitraryXPubKey ctx) $ \k ->         xPubImport net ctx (xPubExport net ctx k) == Just k +hex :: Text -> B.ByteString+hex = fromJust . decodeHex++-- | The address text and the index of an entry of a derivation list.+addressAndIndex :: (Address, a, KeyIndex) -> (Maybe Text, KeyIndex)+addressAndIndex (addr, _, i) = (addrToText btc addr, i)++-- | Master keys of BIP32 test vectors 1 and 2, and the testnet form of the+-- first.+xpub1Text, xprv1Text, tpub1Text, tprv1Text, xpub2Text :: Text+xpub1Text = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"+xprv1Text = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"+tpub1Text = "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp"+tprv1Text = "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m"+xpub2Text = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"++xpub1, xpub2 :: Ctx -> XPubKey+xpub1 ctx = fromJust (xPubImport btc ctx xpub1Text)+xpub2 ctx = fromJust (xPubImport btc ctx xpub2Text)++xprv1 :: Ctx -> XPrvKey+xprv1 _ = fromJust (xPrvImport btc xprv1Text)+ pubKeyOfSubKeyIsSubKeyOfPubKey :: Ctx -> XPrvKey -> Word32 -> Bool pubKeyOfSubKeyIsSubKeyOfPubKey ctx k i =   deriveXPubKey ctx (prvSubKey ctx k i') == pubSubKey ctx (deriveXPubKey ctx k) i'@@ -568,6 +848,49 @@  m3 :: TestKey m3 = "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"++-- Test vector 4 checks that private keys with leading zero bytes are kept at+-- 32 bytes. Each entry is the path, the extended public key and the extended+-- private key.+m4 :: TestKey+m4 = "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678"++vector4 :: [(String, Text, Text)]+vector4 =+  [ ( "m",+      "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa",+      "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv"+    ),+    ( "m/0'",+      "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m",+      "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G"+    ),+    ( "m/0'/1'",+      "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt",+      "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1"+    )+  ]++-- Test vector 5 lists extended keys that must be refused, with the reason.+vector5 :: [(Text, String)]+vector5 =+  [ ("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm", "pubkey version / prvkey mismatch"),+    ("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH", "prvkey version / pubkey mismatch"),+    ("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn", "invalid pubkey prefix 04"),+    ("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ", "invalid prvkey prefix 04"),+    ("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4", "invalid pubkey prefix 01"),+    ("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J", "invalid prvkey prefix 01"),+    ("xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv", "zero depth with non-zero parent fingerprint (xprv)"),+    ("xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ", "zero depth with non-zero parent fingerprint (xpub)"),+    ("xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN", "zero depth with non-zero index (xprv)"),+    ("xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8", "zero depth with non-zero index (xpub)"),+    ("DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4", "unknown extended key version (first)"),+    ("DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9", "unknown extended key version (second)"),+    ("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx", "private key 0 not in 1..n-1"),+    ("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G", "private key n not in 1..n-1"),+    ("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY", "invalid pubkey 020000000000000000000000000000000000000000000000000000000000000007"),+    ("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL", "invalid checksum")+  ]  vector3 :: [TestVector] vector3 =
test/Haskoin/Crypto/Keys/MnemonicSpec.hs view
@@ -8,10 +8,12 @@ import Data.ByteString qualified as BS import Data.Either (fromRight) import Data.List (isPrefixOf)+import Data.Map.Strict qualified as M import Data.Maybe (fromJust) import Data.Serialize (Serialize, encode) import Data.Text (Text) import Data.Text qualified as T+import Data.Vector qualified as V import Data.Word (Word32, Word64) import Haskoin (btc) import Haskoin.Crypto@@ -30,24 +32,75 @@     it "mnemonic sentence with invalid checksum" fromMnemonicInvalidTest     it "empty mnemonic sentence is invalid" $ sequence_ [emptyMnemonicTest]     it "generate 12 words" $ property toMnemonic128-    it "generate 18 words" $ property toMnemonic160+    it "generate 15 words" $ property toMnemonic160     it "generate 24 words" $ property toMnemonic256-    it "generate 48 words" $ property toMnemonic512-    it "generate any number of words" $ property toMnemonicVar+    it "refuses 64 bytes of entropy" $ property toMnemonic512+    it "generate any allowed number of words" toMnemonicVar+    it "refuses entropy below 16 bytes" toMnemonicTooShort     it "encode and decode 128-bit entropy" $ property fromToMnemonic128     it "encode and decode 160-bit entropy" $ property fromToMnemonic160     it "encode and decode 256-bit entropy" $ property fromToMnemonic256-    it "encode and decode 512-bit entropy" $ property fromToMnemonic512-    it "encode and decode n-bit entropy" $ property fromToMnemonicVar+    it "encode and decode n-bit entropy" fromToMnemonicVar     it "convert 128-bit mnemonic to seed" $ property mnemonicToSeed128     it "convert 160-bit mnemonic to seed" $ property mnemonicToSeed160     it "convert 256-bit mnemonic to seed" $ property mnemonicToSeed256-    it "convert 512-bit mnemonic to seed" $ property mnemonicToSeed512-    it "convert n-bit mnemonic to seed" $ property mnemonicToSeedVar+    it "convert n-bit mnemonic to seed" mnemonicToSeedVar     it "get bits" $ property getBitsByteCount     it "get end bits" $ property getBitsEndBits     it "passes the Trezor vectors" trezorVectorTests+    it "refuses entropy of 4 bytes" $+      toMnemonic (BS.replicate 4 0)+        `shouldBe` Left "toMnemonic: minimum entropy is 16 bytes (128 bits)"+    it "refuses entropy of 64 bytes" $+      toMnemonic (BS.replicate 64 0)+        `shouldBe` Left "toMnemonic: maximum entropy is 32 bytes (256 bits)"+    it "refuses entropy that is not a multiple of 4 bytes" $+      toMnemonic (BS.replicate 17 0)+        `shouldBe` Left "toMnemonic: entropy must be multiples of 4 bytes"+    it "gives 12 to 24 words for 16 to 32 bytes of entropy" $+      -- (bytes * 8 + bytes * 8 / 32) / 11 words: 16 bytes give 132 / 11 = 12+      forM_ [(16, 12), (20, 15), (24, 18), (28, 21), (32, 24)] $ \(bytes, count) ->+        fmap (length . T.words) (toMnemonic (BS.replicate bytes 0)) `shouldBe` Right count+    it "refuses a sentence of 3 words" $+      fromMnemonic "abandon abandon ability"+        `shouldBe` Left "fromMnemonic: wrong number of words: 3"+    it "refuses a sentence of 48 words" $+      fromMnemonic (T.unwords (replicate 48 "abandon"))+        `shouldBe` Left "fromMnemonic: wrong number of words: 48"+    it "refuses a sentence of only whitespace" $+      fromMnemonic "   " `shouldBe` Left "fromMnemonic: wrong number of words: 0"+    it "refuses a word that is not in the list" $+      fromMnemonic "Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"+        `shouldBe` Left "fromMnemonic: words not in the list: Abandon"+    it "matches words after NFKD normalisation" $+      -- the first word in fullwidth letters, which NFKD maps back to ASCII+      fmap encodeHex (fromMnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about")+        `shouldBe` Right "00000000000000000000000000000000"+    it "normalises the passphrase to NFKD before deriving the seed" $ do+      -- PBKDF2-HMAC-SHA512, 2048 rounds, of the sentence with the salt+      -- "mnemonic" ++ NFKD("café"), as python-mnemonic computes it. The+      -- precomposed and the decomposed spelling must give the same seed.+      fmap encodeHex (mnemonicToSeed "caf\x00e9" abandon12) `shouldBe` Right cafeSeed+      fmap encodeHex (mnemonicToSeed "cafe\x0301" abandon12) `shouldBe` Right cafeSeed+    it "has the 2048 BIP39 words in order" $ do+      V.length wordList `shouldBe` 2048+      wordList V.! 0 `shouldBe` "abandon"+      wordList V.! 2047 `shouldBe` "zoo"+      M.size wordListMap `shouldBe` 2048+      M.lookup "abandon" wordListMap `shouldBe` Just 0+      M.lookup "zoo" wordListMap `shouldBe` Just 2047 +-- | The first Trezor sentence, for 16 zero bytes of entropy.+abandon12 :: Mnemonic+abandon12 =+  "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"++-- | Seed of 'abandon12' with the passphrase "café".+cafeSeed :: Text+cafeSeed =+  "af8bbd2566df7b69d926f2b09dfdbd75db6c994a3399b2cc65f928d63e3fd4e6\+  \1218ee0d15f8c810be4d45e66d47b43c15a5cc753976b1666912377ff7ae9818"+ toMnemonicTest :: Assertion toMnemonicTest = zipWithM_ f ents mss   where@@ -457,9 +510,11 @@         . fromRight (error "Could not decode mnemonic sentence")         $ toMnemonic bs +-- 64 bytes of entropy are above the 32 bytes BIP39 allows. toMnemonic512 ::   ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool-toMnemonic512 ((a, b, c, d), (e, f, g, h)) = l == 48+toMnemonic512 ((a, b, c, d), (e, f, g, h)) =+  toMnemonic bs == Left "toMnemonic: maximum entropy is 32 bytes (256 bits)"   where     bs =       BS.concat@@ -472,25 +527,32 @@           encode g,           encode h         ]-    l =-      length-        . T.words-        . fromRight (error "Could not decode mnemonic sentence")-        $ toMnemonic bs -toMnemonicVar :: [Word32] -> Property-toMnemonicVar ls = not (null ls) && length ls <= 8 ==> l == wc-  where-    bs = binWordsToBS ls-    bl = BS.length bs-    cb = bl `div` 4-    wc = (cb + bl * 8) `div` 11-    l =-      length-        . T.words-        . fromRight (error "Could not decode mnemonic sentence")-        $ toMnemonic bs+-- Fewer than 4 words of 32 bits are below the 16 bytes BIP39 allows.+toMnemonicTooShort :: Property+toMnemonicTooShort =+  forAll (choose (0, 3) >>= \n -> vectorOf n (arbitrary :: Gen Word32)) $ \ls ->+    toMnemonic (binWordsToBS ls)+      == Left "toMnemonic: minimum entropy is 16 bytes (128 bits)" +-- | Entropy of 16 to 32 bytes in steps of 4 bytes, the sizes BIP39 allows.+allowedEntropy :: Gen BS.ByteString+allowedEntropy = do+  n <- choose (4, 8)+  binWordsToBS <$> vectorOf n (arbitrary :: Gen Word32)++-- Every 32 bits of entropy add one checksum bit, and every 11 bits give a word.+toMnemonicVar :: Property+toMnemonicVar = forAll allowedEntropy $ \bs ->+  let bl = BS.length bs+      wc = (bl * 8 + bl `div` 4) `div` 11+      l =+        length+          . T.words+          . fromRight (error "Could not decode mnemonic sentence")+          $ toMnemonic bs+   in l == wc+ {- Encode/Decode -}  fromToMnemonic128 :: (Word64, Word64) -> Bool@@ -520,34 +582,12 @@         (error "Could not decode mnemonic entropy")         (fromMnemonic =<< toMnemonic bs) -fromToMnemonic512 ::-  ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool-fromToMnemonic512 ((a, b, c, d), (e, f, g, h)) = bs == bs'-  where-    bs =-      BS.concat-        [ encode a,-          encode b,-          encode c,-          encode d,-          encode e,-          encode f,-          encode g,-          encode h-        ]-    bs' =-      fromRight-        (error "Could not decode mnemonic entropy")-        (fromMnemonic =<< toMnemonic bs)--fromToMnemonicVar :: [Word32] -> Property-fromToMnemonicVar ls = not (null ls) && length ls <= 8 ==> bs == bs'-  where-    bs = binWordsToBS ls-    bs' =-      fromRight-        (error "Could not decode mnemonic entropy")-        (fromMnemonic =<< toMnemonic bs)+fromToMnemonicVar :: Property+fromToMnemonicVar = forAll allowedEntropy $ \bs ->+  fromRight+    (error "Could not decode mnemonic entropy")+    (fromMnemonic =<< toMnemonic bs)+    == bs  {- Mnemonic to seed -} @@ -581,36 +621,14 @@         (mnemonicToSeed "" =<< toMnemonic bs)     l = BS.length seed -mnemonicToSeed512 ::-  ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool-mnemonicToSeed512 ((a, b, c, d), (e, f, g, h)) = l == 64-  where-    bs =-      BS.concat-        [ encode a,-          encode b,-          encode c,-          encode d,-          encode e,-          encode f,-          encode g,-          encode h-        ]-    seed =-      fromRight-        (error "Could not decode mnemonic seed")-        (mnemonicToSeed "" =<< toMnemonic bs)-    l = BS.length seed--mnemonicToSeedVar :: [Word32] -> Property-mnemonicToSeedVar ls = not (null ls) && length ls <= 16 ==> l == 64-  where-    bs = binWordsToBS ls-    seed =-      fromRight+mnemonicToSeedVar :: Property+mnemonicToSeedVar = forAll allowedEntropy $ \bs ->+  BS.length+    ( fromRight         (error "Could not decode mnemonic seed")         (mnemonicToSeed "" =<< toMnemonic bs)-    l = BS.length seed+    )+    == 64  {- Get bits from ByteString -} 
test/Haskoin/Crypto/KeysSpec.hs view
@@ -15,11 +15,13 @@ import Data.Bytes.Put import Data.Bytes.Serial import Data.Default (def)+import Data.Either (isLeft) import Data.Maybe import Data.Serialize qualified as S import Data.String (fromString) import Data.String.Conversions (cs) import Data.Text (Text)+import Data.Text qualified as T import Haskoin.Address import Haskoin.Crypto import Haskoin.Network.Constants@@ -36,7 +38,6 @@   def     { readTests =         [ ReadBox (arbitrary :: Gen SecKey),-          ReadBox arbitraryPrivateKey,           ReadBox (arbitraryPublicKey ctx)         ],       marshalJsonTests =@@ -57,9 +58,9 @@     testIdentity $ identityTests ctx     prop "Public key is canonical" $       forAll (arbitraryKeyPair ctx) (isCanonicalPubKey ctx . snd)-    prop "Key pair key show . read identity" $-      forAll (arbitraryKeyPair ctx) $ \x ->-        (read . show) x == x+    prop "Public key show . read identity" $+      forAll (arbitraryKeyPair ctx) $ \(_, pub) ->+        (read . show) pub == pub     prop "Public key binary serialization" $ do       forAll (arbitraryKeyPair ctx) $ \(sec, pub) ->         (unmarshal ctx . marshal ctx) pub == Right pub@@ -80,6 +81,111 @@     it "Passes deterministic signing tests" $ testDetSigning ctx   describe "MiniKey vectors" $     it "Passes MiniKey decoding tests" testMiniKey+  describe "Secret key range" $ do+    it "accepts the smallest secret key, 1" $+      decodeSecKey (B.replicate 31 0 <> B.singleton 1) `shouldBe` Just secKey1+    it "accepts the largest secret key, n - 1" $+      decodeSecKey (hex "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")+        `shouldSatisfy` isJust+    it "refuses the zero key" $+      decodeSecKey (B.replicate 32 0) `shouldBe` Nothing+    it "refuses the curve order n" $+      decodeSecKey (hex "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")+        `shouldBe` Nothing+    it "refuses a key that is not 32 bytes" $ do+      decodeSecKey (B.replicate 31 1) `shouldBe` Nothing+      decodeSecKey (B.replicate 33 1) `shouldBe` Nothing+  describe "WIF" $ do+    it "decodes the compressed WIF of key 1" $+      fromWif btc "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"+        `shouldBe` Just (wrapSecKey True secKey1)+    it "decodes the uncompressed WIF of key 1" $+      fromWif btc "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf"+        `shouldBe` Just (wrapSecKey False secKey1)+    it "refuses the WIF of key 0" $ do+      fromWif btc "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73Nd2Mcv1" `shouldBe` Nothing+      fromWif btc "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAbuatmU" `shouldBe` Nothing+    it "refuses the WIF of the curve order n" $ do+      fromWif btc "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFqRzExJJ" `shouldBe` Nothing+      fromWif btc "5Km2kuu7vtFDPpxywn4u3NLpbr5jKpTB3jsuDU2KYEqetwr388P" `shouldBe` Nothing+    it "refuses a compressed WIF whose flag byte is not 0x01" $+      -- key 1 followed by the flag byte 0x02+      fromWif btc "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sfZr2ym" `shouldBe` Nothing+    it "refuses the WIF of another network" $ do+      -- key 1 with the testnet prefix 0xef+      fromWif btc "cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA" `shouldBe` Nothing+      fromWif btcTest "cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA"+        `shouldBe` Just (wrapSecKey True secKey1)+    it "refuses a Base58Check string with no payload" $+      -- "3QJmnh" is the Base58Check encoding of the empty string+      fromWif btc "3QJmnh" `shouldBe` Nothing+    it "refuses a string longer than any WIF" $+      fromWif btc (T.replicate 53 "z") `shouldBe` Nothing+    it "fails the JSON parse of an invalid WIF instead of throwing" $ do+      (unmarshalJSON btc "\"3QJmnh\"" :: Maybe PrivateKey) `shouldBe` Nothing+      (unmarshalJSON btc "\"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73Nd2Mcv1\"" :: Maybe PrivateKey)+        `shouldBe` Nothing+  describe "Mini keys" $ do+    it "decodes a 22-character mini key" $+      -- the example key of the Bitcoin wiki+      fromMiniKey "S4b3N3oGqDqR5jNuxEvDwf"+        `shouldBe` Just (wrapSecKey False "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d")+    it "refuses a mini key whose check byte is not zero" $+      -- SHA-256 of "S4b3N3oGqDqR5jNuxEvDwg?" starts with 0x8f+      fromMiniKey "S4b3N3oGqDqR5jNuxEvDwg" `shouldBe` Nothing+    it "refuses a mini key of another length" $+      fromMiniKey "S4b3N3oGqDqR5jNuxEvDw" `shouldBe` Nothing+  describe "Key tweaks" $ do+    it "adding a zero tweak leaves the secret key unchanged" $+      tweakSecKey ctx secKey1 zeroHash `shouldBe` Just secKey1+    it "adding one to secret key 1 gives secret key 2" $+      tweakSecKey ctx secKey1 oneHash+        `shouldBe` Just "0000000000000000000000000000000000000000000000000000000000000002"+    it "refuses a tweak that brings the secret key to zero" $+      -- 1 + (n - 1) = n, which is 0 modulo n+      tweakSecKey ctx secKey1 orderMinusOneHash `shouldBe` Nothing+    it "refuses a tweak at or above the curve order" $+      tweakSecKey ctx secKey1 orderHash `shouldBe` Nothing+    it "adding one to the public key of 1 gives the public key of 2" $+      tweakPubKey ctx (pubKey1 ctx) oneHash `shouldBe` Just (pubKey2 ctx)+    it "refuses a tweak that brings the public key to infinity" $+      tweakPubKey ctx (pubKey1 ctx) orderMinusOneHash `shouldBe` Nothing+    prop "tweaking the public key matches tweaking the secret key" $+      forAll arbitraryPrivateKey $ \k ->+        forAll arbitraryHash256 $ \t ->+          (derivePubKey ctx <$> tweakSecKey ctx k.key t)+            == tweakPubKey ctx (derivePubKey ctx k.key) t+  describe "Public key text forms" $ do+    it "parses a compressed public key from JSON" $+      unmarshalJSON ctx "\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\""+        `shouldBe` Just (wrapPubKey True (pubKey1 ctx))+    it "parses an uncompressed public key from JSON" $+      unmarshalJSON+        ctx+        "\"0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\+        \483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8\""+        `shouldBe` Just (wrapPubKey False (pubKey1 ctx))+    it "refuses a public key with trailing bytes" $+      (unmarshalJSON ctx "\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798deadbeef\"" :: Maybe PublicKey)+        `shouldBe` Nothing+    it "refuses a public key with an unknown prefix byte" $+      (unmarshalJSON ctx "\"0579be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"" :: Maybe PublicKey)+        `shouldBe` Nothing+    it "refuses a point that is not on the curve" $+      -- the invalid public key of BIP32 test vector 5+      (unmarshalJSON ctx "\"020000000000000000000000000000000000000000000000000000000000000007\"" :: Maybe PublicKey)+        `shouldBe` Nothing+  describe "Binary private key" $+    it "refuses a serialized zero key" $+      (runGetS deserialize (B.replicate 33 0) :: Either String PrivateKey) `shouldSatisfy` isLeft+  describe "Showing a private key" $ do+    -- The secret of key 1 must not appear in the text+    it "does not reveal the secret" $+      show (PrivateKey "0000000000000000000000000000000000000000000000000000000000000001" True)+        `shouldBe` "PrivateKey {key = <redacted>, compress = True}"+    it "nests in parentheses like a derived instance" $+      show (Just (PrivateKey "0000000000000000000000000000000000000000000000000000000000000001" False))+        `shouldBe` "Just (PrivateKey {key = <redacted>, compress = False})"   describe "key_io_valid.json vectors" $ do     vectors <- runIO (readTestFile "key_io_valid.json" :: IO [(Text, Text, A.Value)])     it "Passes the key_io_valid.json vectors" $@@ -88,6 +194,25 @@     vectors <- runIO (readTestFile "key_io_invalid.json" :: IO [[Text]])     it "Passes the key_io_invalid.json vectors" $       mapM_ (testKeyIOInvalidVector ctx) vectors++hex :: Text -> B.ByteString+hex = fromJust . decodeHex++-- | The secret key 1.+secKey1 :: SecKey+secKey1 = "0000000000000000000000000000000000000000000000000000000000000001"++-- | Public keys of the secret keys 1 and 2, that is G and 2G.+pubKey1, pubKey2 :: Ctx -> PubKey+pubKey1 ctx = fromJust $ importPubKey ctx $ hex "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"+pubKey2 ctx = fromJust $ importPubKey ctx $ hex "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"++-- | Tweaks of 0, 1, n - 1 and n, where n is the curve order.+zeroHash, oneHash, orderMinusOneHash, orderHash :: Hash256+zeroHash = "0000000000000000000000000000000000000000000000000000000000000000"+oneHash = "0000000000000000000000000000000000000000000000000000000000000001"+orderMinusOneHash = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"+orderHash = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"  -- github.com/bitcoin/bitcoin/blob/master/src/script.cpp -- from function IsCanonicalPubKey
test/Haskoin/Crypto/SignatureSpec.hs view
@@ -5,9 +5,11 @@ module Haskoin.Crypto.SignatureSpec (spec) where  import Control.Monad+import Data.Aeson qualified as A import Data.Bits (testBit) import Data.ByteString (ByteString) import Data.ByteString qualified as BS+import Data.Either (isLeft) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe@@ -66,7 +68,80 @@       testP2WSHMulsig ctx     it "agrees with BIP143 p2sh-p2wsh multisig example" $       testBip143p2shp2wpkhMulsig ctx+  describe "Strict signature decoding" $ do+    it "accepts the smallest signature, r = 1 and s = 1" $+      -- DER: 30 06 | 02 01 01 | 02 01 01+      fmap (exportSig ctx) (decodeStrictSig ctx (hex "3006020101020101"))+        `shouldBe` Just (hex "3006020101020101")+    it "refuses a signature whose r is zero" $+      decodeStrictSig ctx (hex "3006020100020101") `shouldBe` Nothing+    it "refuses a signature whose s is zero" $+      decodeStrictSig ctx (hex "3006020101020100") `shouldBe` Nothing+    prop "refuses a signature whose s is above half the curve order" $+      forAll (arbitrarySignature ctx) $ \(_, _, s) ->+        decodeStrictSig ctx (exportSig ctx (highSVariant ctx s)) == Nothing+  describe "Binary signature decoding" $ do+    it "decodes the smallest signature" $+      fmap (exportSig ctx) (unmarshal ctx (hex "3006020101020101"))+        `shouldBe` Right (hex "3006020101020101")+    it "refuses an identifier byte other than 0x30" $+      (unmarshal ctx (hex "3106020101020101") :: Either String Sig) `shouldSatisfy` isLeft+    it "refuses the indefinite length form" $+      (unmarshal ctx (hex "3000020101020101") :: Either String Sig) `shouldSatisfy` isLeft+    it "refuses a length of more than one byte" $+      (unmarshal ctx (hex "308106020101020101") :: Either String Sig) `shouldSatisfy` isLeft+    it "refuses a signature cut short" $+      (unmarshal ctx (hex "3006020101") :: Either String Sig) `shouldSatisfy` isLeft+  describe "Signature JSON" $ do+    prop "refuses a high-S signature, like the binary form" $+      forAll (arbitrarySignature ctx) $ \(_, _, s) ->+        (unmarshalJSON ctx (A.encode (encodeHex (exportSig ctx (highSVariant ctx s)))) :: Maybe Sig)+          == Nothing+    it "refuses a signature that is not hex" $+      (unmarshalJSON ctx "\"zz\"" :: Maybe Sig) `shouldBe` Nothing+  describe "High-S signatures" $ do+    prop "are not in canonical half order" $+      forAll (arbitrarySignature ctx) $ \(_, _, s) ->+        not (isCanonicalHalfOrder ctx (highSVariant ctx s))+    prop "still verify after normalisation, as in Bitcoin Core" $+      forAll (arbitrarySignature ctx) $ \(m, key', s) ->+        verifyHashSig ctx m (highSVariant ctx s) (derivePubKey ctx key')+    prop "a signature does not verify for another message" $+      forAll (arbitrarySignature ctx) $ \(m, key', s) ->+        forAll arbitraryHash256 $ \other ->+          other /= m ==> not (verifyHashSig ctx other s (derivePubKey ctx key'))+  describe "Bitcoin Core deterministic signature vectors" $+    it "signs 'Very deterministic message' as Bitcoin Core does" $+      case getImpl ctx of+        Just ImplCore -> do+          let h = doubleSHA256 ("Very deterministic message" :: ByteString)+          encodeHex (exportSig ctx (signHash ctx coreKey1 h))+            `shouldBe` "304402205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d\+                       \022014ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"+          encodeHex (exportSig ctx (signHash ctx coreKey2 h))+            `shouldBe` "3044022052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd5\+                       \022061d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"+        _ -> pendingWith "the vector is for the nonce function of Bitcoin Core" +hex :: Text -> ByteString+hex = fromJust . decodeHex++-- | The keys strSecret1 and strSecret2 of key_tests.cpp in Bitcoin Core.+coreKey1, coreKey2 :: SecKey+coreKey1 = (fromJust (fromWif btc "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj")).key+coreKey2 = (fromJust (fromWif btc "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3")).key++-- | The same signature with s replaced by n - s. It verifies as well, but it+-- is not in the low-S form that BIP62 requires.+highSVariant :: Ctx -> Sig -> Sig+highSVariant ctx s =+  fromJust $ importCompactSig ctx $ fromJust $ compactSig (r <> highS)+  where+    (r, lowS) = BS.splitAt 32 (exportCompactSig ctx s).get+    n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141+    highS = padTo32 (integerToBS (n - bsToInteger lowS))+    padTo32 bs = BS.replicate (32 - BS.length bs) 0 <> bs+ -- github.com/bitcoin/bitcoin/blob/master/src/script.cpp -- from function IsCanonicalSignature testIsCanonical :: Ctx -> Sig -> Bool@@ -250,7 +325,7 @@         "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"     [op0, op1] = (.outpoint) <$> unsignedTx.inputs     sigIn0 = SigInput (PayPK pubKey0) 625000000 op0 sigHashAll Nothing-    Just h = addressHash160 . pubKeyWitnessAddr ctx $ toPubKey ctx key1+    Just h = addressHash160 =<< pubKeyWitnessAddr ctx (toPubKey ctx key1)     sigIn1 = SigInput (PayWitnessPKHash h) 600000000 op1 sigHashAll Nothing     generatedSignedTx = signTx btc ctx unsignedTx [sigIn0, sigIn1] [key0, key1] @@ -291,7 +366,7 @@       secHexKey         "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"     op0 = (head unsignedTx.inputs).outpoint-    Just h = addressHash160 . pubKeyWitnessAddr ctx $ toPubKey ctx key0+    Just h = addressHash160 =<< pubKeyWitnessAddr ctx (toPubKey ctx key0)     sigIn0 = SigInput (PayWitnessPKHash h) 1000000000 op0 sigHashAll Nothing     generatedSignedTx = signNestedWitnessTx btc ctx unsignedTx [sigIn0] [key0] @@ -344,7 +419,7 @@     rdm = PayMulSig (toPubKey ctx <$> keys) 2     sigIn =       SigInput-        (toP2WSH $ encodeOutput ctx rdm)+        (toP2WSH $ either error id $ encodeOutput ctx rdm)         100000000         op0         sigHashAll@@ -435,7 +510,7 @@       ]     Just keys = traverse secHexKey rawKeys     rdm = PayMulSig (toPubKey ctx <$> keys) 6-    sigIn sh = SigInput (toP2WSH $ encodeOutput ctx rdm) 987654321 op0 sh (Just rdm)+    sigIn sh = SigInput (toP2WSH $ either error id $ encodeOutput ctx rdm) 987654321 op0 sh (Just rdm)     sigHashesA = [sigHashAll, sigHashNone, sigHashSingle]     sigHashesB = setAnyoneCanPay <$> sigHashesA     sigIns = sigIn <$> (sigHashesA <> sigHashesB)
test/Haskoin/NetworkSpec.hs view
@@ -1,190 +1,1126 @@ {-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE OverloadedStrings #-}--module Haskoin.NetworkSpec (spec) where--import Data.Bytes.Get-import Data.Bytes.Put-import Data.Bytes.Serial-import Data.Default (def)-import Data.Maybe (fromJust)-import Data.Text (Text)-import Data.Word (Word32)-import Haskoin.Address-import Haskoin.Crypto-import Haskoin.Network-import Haskoin.Network.Constants-import Haskoin.Transaction-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Test.HUnit (Assertion, assertBool, assertEqual)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck--identityTests :: IdentityTests-identityTests =-  def-    { serialTests =-        [ SerialBox arbitraryVarInt,-          SerialBox arbitraryVarString,-          SerialBox arbitraryNetworkAddress,-          SerialBox arbitraryInvType,-          SerialBox arbitraryInvVector,-          SerialBox arbitraryInv1,-          SerialBox arbitraryVersion,-          SerialBox arbitraryAddr1,-          SerialBox arbitraryAlert,-          SerialBox arbitraryReject,-          SerialBox arbitraryRejectCode,-          SerialBox arbitraryGetData,-          SerialBox arbitraryNotFound,-          SerialBox arbitraryPing,-          SerialBox arbitraryPong,-          SerialBox arbitraryMessageCommand,-          SerialBox arbitraryMessageHeader,-          SerialBox arbitraryBloomFlags,-          SerialBox arbitraryBloomFilter,-          SerialBox arbitraryFilterLoad,-          SerialBox arbitraryFilterAdd-        ]-    }--spec :: Spec-spec = prepareContext $ \ctx -> do-  testIdentity identityTests-  describe "Custom identity tests" $ do-    prop "Data.Serialize Encoding for type Message" $-      forAll arbitraryNetwork $ \net ->-        forAll (arbitraryMessage net ctx) $-          customCerealID (getMessage net) (putMessage net)-  describe "bloom filters" $ do-    it "bloom filter vector 1" bloomFilter1-    it "bloom filter vector 2" bloomFilter2-    it "bloom filter vector 3" $ bloomFilter3 ctx-  describe "relevant bloom filter update" $ do-    it "Relevant Update" $ relevantOutputUpdated ctx-    it "Irrelevant Update" $ irrelevantOutputNotUpdated ctx--bloomFilter :: Word32 -> Text -> Assertion-bloomFilter n x = do-  assertBool "Bloom filter doesn't contain vector 1" $ bloomContains f1 v1-  assertBool "Bloom filter contains something it should not" $-    not $-      bloomContains f1 v2-  assertBool "Bloom filter doesn't contain vector 3" $ bloomContains f3 v3-  assertBool "Bloom filter doesn't contain vector 4" $ bloomContains f4 v4-  assertBool "Bloom filter serialization is incorrect" $-    runPutS (serialize f4) == bs-  where-    f0 = bloomCreate 3 0.01 n BloomUpdateAll-    f1 = bloomInsert f0 v1-    f3 = bloomInsert f1 v3-    f4 = bloomInsert f3 v4-    v1 = fromJust $ decodeHex "99108ad8ed9bb6274d3980bab5a85c048f0950c8"-    v2 = fromJust $ decodeHex "19108ad8ed9bb6274d3980bab5a85c048f0950c8"-    v3 = fromJust $ decodeHex "b5a2c786d9ef4658287ced5914b37a1b4aa32eee"-    v4 = fromJust $ decodeHex "b9300670b4c5366e95b2699e8b18bc75e5f729c5"-    bs = fromJust $ decodeHex x--bloomFilter1 :: Assertion-bloomFilter1 = bloomFilter 0 "03614e9b050000000000000001"--bloomFilter2 :: Assertion-bloomFilter2 = bloomFilter 2147483649 "03ce4299050000000100008001"--bloomFilter3 :: Ctx -> Assertion-bloomFilter3 ctx =-  assertBool "Bloom filter serialization is incorrect" $-    runPutS (serialize f2) == bs-  where-    f0 = bloomCreate 2 0.001 0 BloomUpdateAll-    f1 = bloomInsert f0 $ marshal ctx p-    f2 = bloomInsert f1 $ runPutS $ serialize $ fromJust $ addressHash160 $ pubKeyAddr ctx p-    k = fromJust $ fromWif btc "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"-    p = derivePublicKey ctx k-    bs = fromJust $ decodeHex "038fc16b080000000000000001"--relevantOutputUpdated :: Ctx -> Assertion-relevantOutputUpdated ctx =-  assertBool "Bloom filter output updated" $-    any (bloomContains bf2) spendTxInput-  where-    bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll-    relevantOutputHash = fromJust $ decodeHex "03f47604ea2736334151081e13265b4fe38e6fa8"-    bf1 = bloomInsert bf0 relevantOutputHash-    bf2 = fromJust $ bloomRelevantUpdate ctx bf1 relevantTx-    spendTxInput = runPutS . serialize . (.outpoint) <$> spendRelevantTx.inputs--irrelevantOutputNotUpdated :: Ctx -> Assertion-irrelevantOutputNotUpdated ctx = assertEqual "Bloom filter not updated" Nothing bf2-  where-    bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll-    relevantOutputHash = fromJust $ decodeHex "03f47604ea2736334151081e13265b4fe38e6fa8"-    bf1 = bloomInsert bf0 relevantOutputHash-    bf2 = bloomRelevantUpdate ctx bf1 unrelatedTx---- Random transaction (57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9)-relevantTx :: Tx-relevantTx =-  Tx-    { version = 1,-      inputs =-        [ TxIn-            { outpoint = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1,-              script = fromJust $ decodeHex "473044022032fc8eef299b7e94b9a986a6aa2dcb9733ab804bef80df995e443b9c1f8c604202203335df7a2e2b4789451cdb4b2b05a786a81c51519eb6a567fd6fe8cd7b2d33fe014104272502dc63a512dad1473cb82a71be9baf4f4303abd1ff6028fc8a78e1f3aec1218907119dec14f07354850758ff0948e88a904fa411c4df7d5444414ec64ad6",-              sequence = 4294967295-            }-        ],-      outputs =-        [ TxOut {value = 100000000, script = fromJust $ decodeHex "76a91403f47604ea2736334151081e13265b4fe38e6fa888ac"},-          TxOut {value = 107980000, script = fromJust $ decodeHex "76a91481cc186a2f4a69f633ed4bf10ef4a78be13effdd88ac"}-        ],-      witness = [],-      locktime = 0-    }---- Transaction that spends above (fd6e3b693b844aa431fad46765c1aa019a6b13aebfa9dae916b3ffa43283a300)-spendRelevantTx :: Tx-spendRelevantTx =-  Tx-    { version = 1,-      inputs =-        [ TxIn-            { outpoint = OutPoint "57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9" 0,-              script = fromJust $ decodeHex "483045022100ecc334821e4e94cc2fdc841d5ad147d5bb942b993ba81460cc446e0410afa811022015fcbc542b734dbb61a05ec06012095096de5839c50808fe56f2b315e877c20d012103fb64e5792fa586172339b776b7017d3d529358cb73be6406a1fc994228d14f88",-              sequence = 4294967295-            },-          TxIn-            { outpoint = OutPoint "cfee6a8d6e68e8fd16df6fff010afffcd19d7e075aa7b707dd1bae6adc420042" 0,-              script = fromJust $ decodeHex "47304402200e6bb95fa606f254d17089d83c4ceeb19c5d1699b4faddcd4f1f1568286e6b650220087fb8439f31e1b30e47710d095422405f601d6151f2f93e125e1a08a6e29ad4012103b49252e8fc6d5b49c8d14ee71fab45591df4a126a6c453c724f3d356e38f0cee",-              sequence = 4294967295-            }-        ],-      outputs =-        [ TxOut {value = 3851100, script = fromJust $ decodeHex "76a914a297cae82a9a3b932bf023ae274fe2585295c9ca88ac"},-          TxOut {value = 111000000, script = fromJust $ decodeHex "76a9148f952c38600a61385974acc30a64f74407f9801488ac"}-        ],-      witness = [],-      locktime = 0-    }---- This random transaction is unrelated to the others-unrelatedTx :: Tx-unrelatedTx =-  Tx-    { version = 1,-      inputs =-        [ TxIn-            { outpoint = OutPoint "3ec3a71431c68e5d978a5fb4a0a1081d8bee8384d8aa4c06b1fbaf9413e2214f" 20,-              script = fromJust $ decodeHex "483045022100ec9c202c9d3140b973aca9d7f21a82138aa4cfa43fddc5419098ac5e26a6f152022010848fd688f290ae010fb5cb493410caa03145fc12445900ec1ad2bde33aecd9012102c7445e72d723f99a0064526c28269d07f47c8fd81531a94a8d3bf5ebd5e23ef1",-              sequence = 4294967295-            }-        ],-      outputs =-        [ TxOut {value = 12600000, script = fromJust $ decodeHex "76a9148fef3b7051de8cc44e966159e7ea37f4520187e888ac"}-        ],-      witness = [],-      locktime = 0-    }+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-}++module Haskoin.NetworkSpec (spec) where++import Control.Exception (evaluate)+import Control.Monad (forM_)+import Data.ByteString (ByteString)+import Data.ByteString qualified as B+import Data.Bytes.Get+import Data.Bytes.Put+import Data.Bytes.Serial+import Data.Default (def)+import Data.List (isInfixOf, nub)+import Data.Maybe (fromJust)+import Data.Sequence qualified as S+import Data.Text (Text)+import Data.Text qualified as T+import Data.Word (Word32)+import Haskoin.Address+import Haskoin.Block+import Haskoin.Crypto+import Haskoin.Network+import Haskoin.Script+import Haskoin.Transaction+import Haskoin.Util+import Haskoin.Util.Arbitrary+import Network.Socket (SockAddr (..), tupleToHostAddress)+import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck++-- Helpers -------------------------------------------------------------------++-- | Bytes of a hex string.+hex :: Text -> ByteString+hex = fromJust . decodeHex++-- | Hex of the serialization of a value.+hexOf :: (Serial a) => a -> Text+hexOf = encodeHex . runPutS . serialize++-- | Parse bytes as a value.+parse :: (Serial a) => ByteString -> Either String a+parse = runGetS deserialize++-- | The parse must fail and the error must mention the given words.+failsWith :: (Show a) => Either String a -> String -> Expectation+failsWith result msg =+  case result of+    Left e | msg `isInfixOf` e -> return ()+    _ ->+      expectationFailure $+        "expected a failure mentioning " ++ show msg ++ ", got " ++ show result++-- Hashes whose hex is the same read in either direction, so their bytes on+-- the wire are the hex below whether or not the type reverses them.+h1, h2, h3 :: Hash256+h1 = fromString' (T.replicate 32 "11")+h2 = fromString' (T.replicate 32 "22")+h3 = fromString' (T.replicate 32 "33")++fromString' :: Text -> Hash256+fromString' = either error id . runGetS deserialize . hex++h1Hex, h2Hex, h3Hex :: Text+h1Hex = T.replicate 32 "11"+h2Hex = T.replicate 32 "22"+h3Hex = T.replicate 32 "33"++bh1 :: BlockHash+bh1 = "1111111111111111111111111111111111111111111111111111111111111111"++bh1Hex :: Text+bh1Hex = T.replicate 32 "11"++-- | 10.0.0.1 port 8333 as an IPv4-mapped IPv6 address.+ipv4Host :: HostAddress+ipv4Host = sockToHostAddress (SockAddrInet 8333 (tupleToHostAddress (10, 0, 0, 1)))++-- | Ten zero bytes, two 0xff bytes, the four address bytes, then the port in+-- network byte order.+ipv4Bytes :: Text+ipv4Bytes = "00000000000000000000ffff0a000001208d"++-- | 2001:db8::1 port 8333.+ipv6Host :: HostAddress+ipv6Host = sockToHostAddress (SockAddrInet6 8333 0 (0x20010db8, 0, 0, 1) 0)++ipv6Bytes :: Text+ipv6Bytes = "20010db8000000000000000000000001208d"++-- | 0.0.0.0 port 0, the address of the documentation's version message.+zeroHost :: HostAddress+zeroHost = sockToHostAddress (SockAddrInet 0 (tupleToHostAddress (0, 0, 0, 0)))++-- | The sample version message of the Bitcoin wiki protocol documentation,+-- sent with protocol 60002, before the relay flag existed.+versionPayloadHex :: Text+versionPayloadHex =+  "62ea0000" -- protocol 60002+    <> "0100000000000000" -- services NODE_NETWORK+    <> "11b2d05000000000" -- timestamp 1355854353+    <> "010000000000000000000000000000000000ffff000000000000" -- addr_recv+    <> "010000000000000000000000000000000000ffff000000000000" -- addr_from+    <> "3b2eb35d8ce61765" -- nonce+    <> "0f2f5361746f7368693a302e372e322f" -- "/Satoshi:0.7.2/"+    <> "c03e0300" -- start height 212672++versionMessageHex :: Text+versionMessageHex =+  "f9beb4d9" -- mainnet magic+    <> "76657273696f6e0000000000" -- "version"+    <> "64000000" -- 100 payload bytes+    <> "3b648d5a" -- checksum+    <> versionPayloadHex++wikiVersion :: Version+wikiVersion =+  Version+    { version = 60002,+      services = 1,+      timestamp = 1355854353,+      addrRecv = NetworkAddress 1 zeroHost,+      addrSend = NetworkAddress 1 zeroHost,+      nonce = 7284544412836900411, -- 0x6517e68c5db32e3b+      userAgent = VarString "/Satoshi:0.7.2/",+      startHeight = 212672,+      relay = True+    }++-- | The sample addr message of the Bitcoin wiki protocol documentation:+-- one entry, seen at 1292899810, services NODE_NETWORK, 10.0.0.1:8333.+addrMessageHex :: Text+addrMessageHex =+  "f9beb4d9"+    <> "61646472" <> "0000000000000000" -- "addr"+    <> "1f000000" -- 31 payload bytes+    <> "ed52399b" -- checksum+    <> "01" -- one entry+    <> "e215104d" -- time 1292899810+    <> "0100000000000000" -- services+    <> ipv4Bytes++-- | A ping with nonce 1. The checksum is the first four bytes of the double+-- SHA-256 of the eight payload bytes.+pingMessageHex :: Text+pingMessageHex =+  "f9beb4d9"+    <> "70696e670000000000000000" -- "ping"+    <> "08000000"+    <> "08533f6b"+    <> "0100000000000000"++-- | A verack. The checksum of an empty payload is 5df6e0e2.+verackMessageHex :: Text+verackMessageHex = "f9beb4d9" <> "76657261636b000000000000" <> "00000000" <> "5df6e0e2"++-- | Pay-to-pubkey-hash output script for the hash+-- 03f47604ea2736334151081e13265b4fe38e6fa8.+p2pkhHash :: ByteString+p2pkhHash = hex "03f47604ea2736334151081e13265b4fe38e6fa8"++p2pkhScript :: ByteString+p2pkhScript = hex "76a914" <> p2pkhHash <> hex "88ac"++-- | A transaction with one input and the given output scripts, each paying+-- 1000 satoshi.+txPaying :: [ByteString] -> Tx+txPaying scripts =+  Tx+    { version = 1,+      inputs =+        [ TxIn+            { outpoint = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1,+              script = B.empty,+              sequence = 0xffffffff+            }+        ],+      outputs = [TxOut {value = 1000, script = s} | s <- scripts],+      witness = [],+      locktime = 0+    }++-- | The bytes of the outpoint of an output of a transaction, as inserted in+-- a bloom filter.+outpointBytes :: Tx -> Word32 -> ByteString+outpointBytes tx i = runPutS $ serialize $ OutPoint (txHash tx) i+++-- | 'txPaying' [p2pkhScript] serialized: version, one input (outpoint hash+-- in internal byte order, index 1, empty script, sequence), one output+-- (1000 satoshi, 25-byte script), lock time.+oneInputTxHex :: Text+oneInputTxHex =+  "01000000"+    <> "01"+    <> "0e065dc971ac296141cb9db1cd520caf2fc06aa06fb5202959afe3b71790fe35"+    <> "01000000"+    <> "00"+    <> "ffffffff"+    <> "01"+    <> "e803000000000000"+    <> "19"+    <> "76a91403f47604ea2736334151081e13265b4fe38e6fa888ac"+    <> "00000000"++genesisHeaderHex :: Text+genesisHeaderHex =+  "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"++knownCommands :: [MessageCommand]+knownCommands =+  [ MCVersion,+    MCVerAck,+    MCAddr,+    MCAddrV2,+    MCSendAddrV2,+    MCInv,+    MCGetData,+    MCNotFound,+    MCGetBlocks,+    MCGetHeaders,+    MCTx,+    MCBlock,+    MCMerkleBlock,+    MCHeaders,+    MCGetAddr,+    MCFilterLoad,+    MCFilterAdd,+    MCFilterClear,+    MCPing,+    MCPong,+    MCAlert,+    MCMempool,+    MCReject,+    MCSendHeaders,+    MCFeeFilter,+    MCSendCmpct,+    MCCmpctBlock,+    MCGetBlockTxn,+    MCBlockTxn,+    MCGetCFilters,+    MCCFilter,+    MCGetCFHeaders,+    MCCFHeaders,+    MCGetCFCheckpt,+    MCCFCheckpt,+    MCWtxidRelay,+    MCSendTxRcncl+  ]++-- Identity tests --------------------------------------------------------------++identityTests :: IdentityTests+identityTests =+  def+    { readTests =+        [ ReadBox arbitraryVarInt,+          ReadBox arbitraryVarString,+          ReadBox arbitraryAddressNetwork,+          ReadBox arbitraryNetworkAddressV2,+          ReadBox arbitraryInvType,+          ReadBox arbitraryAlert,+          ReadBox arbitraryFeeFilter,+          ReadBox arbitrarySendTxRcncl,+          ReadBox arbitraryReject,+          ReadBox arbitraryRejectCode,+          ReadBox arbitraryPing,+          ReadBox arbitraryPong,+          ReadBox arbitraryMessageCommand,+          ReadBox arbitraryBloomFlags,+          ReadBox arbitraryBloomFilter,+          ReadBox arbitraryFilterLoad,+          ReadBox arbitraryFilterAdd,+          ReadBox arbitrarySendCompact+        ],+      serialTests =+        [ SerialBox arbitraryVarInt,+          SerialBox arbitraryVarString,+          SerialBox arbitraryNetworkAddress,+          SerialBox arbitraryNetworkAddressV2,+          SerialBox arbitraryAddrV2,+          SerialBox arbitraryInvType,+          SerialBox arbitraryInvVector,+          SerialBox arbitraryInv1,+          SerialBox arbitraryVersion,+          SerialBox arbitraryAddr1,+          SerialBox arbitraryAlert,+          SerialBox arbitraryFeeFilter,+          SerialBox arbitrarySendTxRcncl,+          SerialBox arbitraryReject,+          SerialBox arbitraryRejectCode,+          SerialBox arbitraryGetData,+          SerialBox arbitraryNotFound,+          SerialBox arbitraryPing,+          SerialBox arbitraryPong,+          SerialBox arbitraryMessageCommand,+          SerialBox arbitraryMessageHeader,+          SerialBox arbitraryBloomFlags,+          SerialBox arbitraryBloomFilter,+          SerialBox arbitraryFilterLoad,+          SerialBox arbitraryFilterAdd,+          SerialBox arbitrarySendCompact,+          SerialBox arbitraryGetBlockTxn,+          SerialBox arbitraryGetCFilters,+          SerialBox arbitraryCFilter,+          SerialBox arbitraryGetCFHeaders,+          SerialBox arbitraryCFHeaders,+          SerialBox arbitraryGetCFCheckpt,+          SerialBox arbitraryCFCheckpt+        ]+    }++spec :: Spec+spec = prepareContext $ \ctx -> do+  testIdentity identityTests+  describe "Custom identity tests" $ do+    prop "Data.Serialize Encoding for type Message" $+      forAll arbitraryNetwork $ \net ->+        forAll (arbitraryMessage net ctx) $+          customCerealID (getMessage net) (putMessage net)+    prop "Binary encoding/decoding identity for CompactBlock" $+      forAll arbitraryNetwork $ \net ->+        forAll (arbitraryCompactBlock net ctx) $ \c ->+          parse (runPutS (serialize c)) == Right c+    prop "Binary encoding/decoding identity for BlockTxn" $+      forAll arbitraryNetwork $ \net ->+        forAll (arbitraryBlockTxn net ctx) $ \b ->+          parse (runPutS (serialize b)) == Right b+    prop "msgType is the command a message is sent with" $+      forAll arbitraryNetwork $ \net ->+        forAll (arbitraryMessage net ctx) $ \m ->+          fmap (.cmd) (parse (runPutS (putMessage net m)) :: Either String MessageHeader)+            == Right (msgType m)+  varIntSpec+  listSpec+  commandSpec+  inventorySpec+  addressSpec+  addrV2Spec+  versionSpec+  smallMessagesSpec+  rejectSpec+  framingSpec+  bloomSpec ctx+  compactBlockSpec+  blockFilterSpec+  constantsSpec++-- VarInt ----------------------------------------------------------------------++varIntSpec :: Spec+varIntSpec = describe "VarInt" $ do+  it "writes the shortest encoding at every boundary" $ do+    hexOf (VarInt 0) `shouldBe` "00"+    hexOf (VarInt 0xfc) `shouldBe` "fc"+    hexOf (VarInt 0xfd) `shouldBe` "fdfd00"+    hexOf (VarInt 0xffff) `shouldBe` "fdffff"+    hexOf (VarInt 0x10000) `shouldBe` "fe00000100"+    hexOf (VarInt 0xffffffff) `shouldBe` "feffffffff"+    hexOf (VarInt 0x100000000) `shouldBe` "ff0000000001000000"+    hexOf (VarInt 0xffffffffffffffff) `shouldBe` "ffffffffffffffffff"+  it "reads each encoding back" $ do+    parse (hex "00") `shouldBe` Right (VarInt 0)+    parse (hex "fc") `shouldBe` Right (VarInt 0xfc)+    parse (hex "fdfd00") `shouldBe` Right (VarInt 0xfd)+    parse (hex "fdffff") `shouldBe` Right (VarInt 0xffff)+    parse (hex "fe00000100") `shouldBe` Right (VarInt 0x10000)+    parse (hex "feffffffff") `shouldBe` Right (VarInt 0xffffffff)+    parse (hex "ff0000000001000000") `shouldBe` Right (VarInt 0x100000000)+    parse (hex "ffffffffffffffffff") `shouldBe` Right (VarInt 0xffffffffffffffff)+  it "refuses a three-byte encoding of a value below 253" $+    (parse (hex "fd0100") :: Either String VarInt) `failsWith` "non-canonical"+  it "refuses a five-byte encoding of a value below 0x10000" $+    (parse (hex "fe01000000") :: Either String VarInt) `failsWith` "non-canonical"+  it "refuses a nine-byte encoding of a value below 0x100000000" $+    (parse (hex "ff0100000000000000") :: Either String VarInt) `failsWith` "non-canonical"+  it "writes an Int with putVarInt like a VarInt" $+    encodeHex (runPutS (putVarInt (300 :: Int))) `shouldBe` "fd2c01"++-- Lists and byte strings -----------------------------------------------------++listSpec :: Spec+listSpec = describe "lists and byte strings" $ do+  it "limits counts to the MAX_SIZE of Bitcoin Core" $+    maxListSize `shouldBe` 33554432+  it "writes a byte string with its length in front" $ do+    hexOf (VarString "abc") `shouldBe` "03616263"+    hexOf (VarString "") `shouldBe` "00"+  it "reads a byte string" $+    parse (hex "03616263") `shouldBe` Right (VarString "abc")+  it "refuses a byte count above the limit" $+    -- 0x02000001 is one above the limit+    (parse (hex "fe01000002") :: Either String VarString) `failsWith` "Byte count too large"+  it "writes bytes with their count" $+    encodeHex (runPutS (putVarIntBytes "abc")) `shouldBe` "03616263"+  it "reads a list with its count" $+    runGetS (getVarIntList getWord8) (hex "020102") `shouldBe` Right [1, 2]+  it "writes a list with its count" $+    encodeHex (runPutS (putVarIntList putWord8 [1, 2])) `shouldBe` "020102"+  it "refuses a list count above the limit" $+    runGetS (getVarIntList getWord8) (hex "fe01000002") `failsWith` "List count too large"++-- Message commands -----------------------------------------------------------++commandSpec :: Spec+commandSpec = describe "message commands" $ do+  it "names every known command as Bitcoin Core does" $+    map commandToString knownCommands+      `shouldBe` [ "version",+                   "verack",+                   "addr",+                   "addrv2",+                   "sendaddrv2",+                   "inv",+                   "getdata",+                   "notfound",+                   "getblocks",+                   "getheaders",+                   "tx",+                   "block",+                   "merkleblock",+                   "headers",+                   "getaddr",+                   "filterload",+                   "filteradd",+                   "filterclear",+                   "ping",+                   "pong",+                   "alert",+                   "mempool",+                   "reject",+                   "sendheaders",+                   "feefilter",+                   "sendcmpct",+                   "cmpctblock",+                   "getblocktxn",+                   "blocktxn",+                   "getcfilters",+                   "cfilter",+                   "getcfheaders",+                   "cfheaders",+                   "getcfcheckpt",+                   "cfcheckpt",+                   "wtxidrelay",+                   "sendtxrcncl"+                 ]+  it "reads every known name back to its command" $+    forM_ knownCommands $ \c ->+      stringToCommand (commandToString c) `shouldBe` c+  it "reads an unknown name as an unknown command" $+    stringToCommand "foo" `shouldBe` MCOther "foo"+  it "pads a name with NUL bytes to twelve" $ do+    hexOf MCVersion `shouldBe` "76657273696f6e0000000000"+    hexOf MCPing `shouldBe` "70696e670000000000000000"+    hexOf MCSendTxRcncl `shouldBe` "73656e64747872636e636c00"+    hexOf (MCOther "foo") `shouldBe` "666f6f000000000000000000"+  it "reads a padded name" $ do+    parse (hex "76657273696f6e0000000000") `shouldBe` Right MCVersion+    parse (hex "666f6f000000000000000000") `shouldBe` Right (MCOther "foo")+  it "reads twelve NUL bytes as an empty unknown command" $+    parse (hex "000000000000000000000000") `shouldBe` Right (MCOther "")+  it "refuses a byte after the NUL terminator" $+    (parse "ping\0garbage" :: Either String MessageCommand) `failsWith` "after its NUL terminator"+  it "refuses a name with a byte outside printable ASCII" $+    -- "ping" followed by a line feed+    (parse (hex "70696e670a00000000000000") :: Either String MessageCommand) `failsWith` "printable ASCII"+  it "refuses to serialize a name longer than twelve bytes" $+    evaluate (runPutS (serialize (MCOther "averyverylongcommand"))) `shouldThrow` anyErrorCall+  it "refuses to serialize a name with a byte outside printable ASCII" $+    evaluate (runPutS (serialize (MCOther "ping\n"))) `shouldThrow` anyErrorCall+  it "shows and reads a command as its name" $ do+    show MCPing `shouldBe` "\"ping\""+    read "\"ping\"" `shouldBe` MCPing+    read "\"foo\"" `shouldBe` MCOther "foo"+    ("ping" :: MessageCommand) `shouldBe` MCPing++-- Inventory ------------------------------------------------------------------++inventorySpec :: Spec+inventorySpec = describe "inventory" $ do+  it "numbers every inventory type as Bitcoin Core does" $ do+    hexOf InvError `shouldBe` "00000000"+    hexOf InvTx `shouldBe` "01000000"+    hexOf InvBlock `shouldBe` "02000000"+    hexOf InvMerkleBlock `shouldBe` "03000000"+    hexOf InvCompactBlock `shouldBe` "04000000"+    hexOf InvWTx `shouldBe` "05000000"+    -- MSG_WITNESS_FLAG is 1 << 30, so 0x40000001 little-endian+    hexOf InvWitnessTx `shouldBe` "01000040"+    hexOf InvWitnessBlock `shouldBe` "02000040"+    hexOf InvWitnessMerkleBlock `shouldBe` "03000040"+    hexOf (InvType 7) `shouldBe` "07000000"+  it "reads every inventory type" $ do+    parse (hex "01000000") `shouldBe` Right InvTx+    parse (hex "05000000") `shouldBe` Right InvWTx+    parse (hex "02000040") `shouldBe` Right InvWitnessBlock+  it "reads an unknown inventory type as a number" $+    parse (hex "06000000") `shouldBe` Right (InvType 6)+  it "writes an inventory vector as its type then its hash" $+    hexOf (InvVector InvTx h1) `shouldBe` "01000000" <> h1Hex+  it "writes inv, getdata and notfound as a counted list of vectors" $ do+    hexOf (Inv [InvVector InvTx h1, InvVector InvBlock h2])+      `shouldBe` "02" <> "01000000" <> h1Hex <> "02000000" <> h2Hex+    hexOf (GetData [InvVector InvWitnessTx h1]) `shouldBe` "01" <> "01000040" <> h1Hex+    hexOf (NotFound [InvVector InvTx h1]) `shouldBe` "01" <> "01000000" <> h1Hex+  it "reads inv, getdata and notfound" $ do+    parse (hex ("01" <> "01000000" <> h1Hex)) `shouldBe` Right (Inv [InvVector InvTx h1])+    parse (hex ("01" <> "02000000" <> h1Hex)) `shouldBe` Right (GetData [InvVector InvBlock h1])+    parse (hex ("00")) `shouldBe` Right (NotFound [])++-- Network addresses ----------------------------------------------------------++addressSpec :: Spec+addressSpec = describe "network addresses" $ do+  it "maps an IPv4 address into IPv6 with the port in network byte order" $+    hexOf ipv4Host `shouldBe` ipv4Bytes+  it "reads an IPv4-mapped address back as an IPv4 socket address" $+    fmap hostToSockAddr (parse (hex ipv4Bytes))+      `shouldBe` Right (SockAddrInet 8333 (tupleToHostAddress (10, 0, 0, 1)))+  it "writes an IPv6 address as sixteen bytes then the port" $+    hexOf ipv6Host `shouldBe` ipv6Bytes+  it "reads an IPv6 address back" $+    fmap hostToSockAddr (parse (hex ipv6Bytes))+      `shouldBe` Right (SockAddrInet6 8333 0 (0x20010db8, 0, 0, 1) 0)+  it "refuses an address shorter than 18 bytes" $+    (parse (hex "00000000000000000000ffff0a000001") :: Either String HostAddress)+      `failsWith` "too few bytes"+  it "writes the services before the address" $+    hexOf (NetworkAddress 1 ipv4Host) `shouldBe` "0100000000000000" <> ipv4Bytes+  it "writes an addr entry with its time first" $+    hexOf (Addr [(1292899810, NetworkAddress 1 ipv4Host)])+      `shouldBe` "01" <> "e215104d" <> "0100000000000000" <> ipv4Bytes+  it "reads an addr message" $+    parse (hex ("01" <> "e215104d" <> "0100000000000000" <> ipv4Bytes))+      `shouldBe` Right (Addr [(1292899810, NetworkAddress 1 ipv4Host)])++-- addrv2 ----------------------------------------------------------------------++addrV2Spec :: Spec+addrV2Spec = describe "addrv2" $ do+  let ipv4Entry = NetworkAddressV2 1292899810 1 AddrIPv4 (hex "0a000001") 8333+      ipv4EntryHex = "e215104d" <> "01" <> "01" <> "04" <> "0a000001" <> "208d"+  it "numbers the networks as BIP 155 does" $+    map addressNetworkId [AddrIPv4, AddrIPv6, AddrTorV2, AddrTorV3, AddrI2P, AddrCJDNS, AddrYggdrasil, AddrUnknown 9]+      `shouldBe` [1, 2, 3, 4, 5, 6, 7, 9]+  it "knows the address length of every network" $+    map addressNetworkLength [AddrIPv4, AddrIPv6, AddrTorV2, AddrTorV3, AddrI2P, AddrCJDNS, AddrYggdrasil, AddrUnknown 9]+      `shouldBe` [Just 4, Just 16, Just 10, Just 32, Just 32, Just 16, Just 16, Nothing]+  it "limits an address to 512 bytes" $+    maxAddrV2Size `shouldBe` 512+  it "writes an IPv4 entry with time, services as a VarInt, network, address and port" $+    hexOf ipv4Entry `shouldBe` ipv4EntryHex+  it "writes services of more than one byte as a VarInt" $+    -- 1024 is NODE_NETWORK_LIMITED+    hexOf (NetworkAddressV2 0 1024 AddrIPv6 (B.replicate 16 0) 0)+      `shouldBe` "00000000" <> "fd0004" <> "02" <> "10" <> T.replicate 32 "0" <> "0000"+  it "writes a Tor v3 entry with 32 address bytes and no port" $+    hexOf (NetworkAddressV2 0 0 AddrTorV3 (B.replicate 32 0xab) 0)+      `shouldBe` "00000000" <> "00" <> "04" <> "20" <> T.replicate 32 "ab" <> "0000"+  it "reads an entry" $+    parse (hex ipv4EntryHex) `shouldBe` Right ipv4Entry+  it "refuses an IPv4 entry whose address is not four bytes" $+    (parse (hex ("00000000" <> "00" <> "01" <> "05" <> "0a00000102" <> "208d")) :: Either String NetworkAddressV2)+      `failsWith` "should be 4"+  it "keeps an entry of an unknown network with its bytes" $+    parse (hex ("00000000" <> "00" <> "c8" <> "03" <> "aabbcc" <> "0000"))+      `shouldBe` Right (NetworkAddressV2 0 0 (AddrUnknown 200) (hex "aabbcc") 0)+  it "accepts an address of 512 bytes" $+    parse (hex ("00000000" <> "00" <> "c8" <> "fd0002") <> B.replicate 512 0x11 <> hex "0000")+      `shouldBe` Right (NetworkAddressV2 0 0 (AddrUnknown 200) (B.replicate 512 0x11) 0)+  it "refuses an address of 513 bytes" $+    (parse (hex ("00000000" <> "00" <> "c8" <> "fd0102") <> B.replicate 513 0x11 <> hex "0000") :: Either String NetworkAddressV2)+      `failsWith` "too long"+  it "writes an addrv2 message as a counted list of entries" $+    hexOf (AddrV2 [ipv4Entry, ipv4Entry]) `shouldBe` "02" <> ipv4EntryHex <> ipv4EntryHex+  it "turns an addr entry into an addrv2 entry" $+    toNetworkAddressV2 (1292899810, NetworkAddress 1 ipv4Host) `shouldBe` ipv4Entry+  it "keeps an IPv6 entry as sixteen bytes" $+    toNetworkAddressV2 (0, NetworkAddress 0 ipv6Host)+      `shouldBe` NetworkAddressV2 0 0 AddrIPv6 (hex "20010db8000000000000000000000001") 8333+  it "turns an addrv2 entry back into an addr entry" $ do+    fromNetworkAddressV2 ipv4Entry `shouldBe` Just (1292899810, NetworkAddress 1 ipv4Host)+    fromNetworkAddressV2 (NetworkAddressV2 0 0 AddrIPv6 (hex "20010db8000000000000000000000001") 8333)+      `shouldBe` Just (0, NetworkAddress 0 ipv6Host)+  it "has no addr form for a Tor address" $+    fromNetworkAddressV2 (NetworkAddressV2 0 0 AddrTorV3 (B.replicate 32 0) 0) `shouldBe` Nothing++-- Version ---------------------------------------------------------------------++versionSpec :: Spec+versionSpec = describe "version" $ do+  it "reads the version message of the protocol documentation" $+    (parse (hex versionPayloadHex) :: Either String Version) `shouldBe` Right wikiVersion+  it "reads the relay flag" $ do+    parse (hex (versionPayloadHex <> "00")) `shouldBe` Right wikiVersion {relay = False}+    parse (hex (versionPayloadHex <> "01")) `shouldBe` Right wikiVersion+  it "treats any other relay byte as true, as Bitcoin Core does" $+    parse (hex (versionPayloadHex <> "07")) `shouldBe` Right wikiVersion+  it "writes the relay flag after the start height" $+    hexOf wikiVersion `shouldBe` versionPayloadHex <> "01"+  it "limits the user agent to 256 bytes" $+    maxUserAgentLength `shouldBe` 256+  it "accepts a user agent of 256 bytes" $+    -- the 80 bytes before the user agent, then a 256-byte agent of "A"s+    let ua = hex "fd0001" <> B.replicate 256 0x41+        payload = hex (T.take 160 versionPayloadHex) <> ua <> hex "c03e030001"+     in fmap (\v -> B.length v.userAgent.get) (parse payload :: Either String Version)+          `shouldBe` Right 256+  it "refuses a user agent of 257 bytes" $+    let ua = hex "fd0101" <> B.replicate 257 0x41+        payload = hex (T.take 160 versionPayloadHex) <> ua <> hex "c03e030001"+     in (parse payload :: Either String Version) `failsWith` "too long"++-- Ping, pong, feefilter, sendtxrcncl, alert ----------------------------------++smallMessagesSpec :: Spec+smallMessagesSpec = describe "small messages" $ do+  it "writes ping and pong as a little-endian nonce" $ do+    hexOf (Ping 1) `shouldBe` "0100000000000000"+    hexOf (Pong 0x0102030405060708) `shouldBe` "0807060504030201"+  it "reads ping and pong" $ do+    parse (hex "0100000000000000") `shouldBe` Right (Ping 1)+    parse (hex "0807060504030201") `shouldBe` Right (Pong 0x0102030405060708)+  it "writes feefilter as a little-endian fee rate" $+    hexOf (FeeFilter 1000) `shouldBe` "e803000000000000"+  it "reads feefilter" $+    parse (hex "e803000000000000") `shouldBe` Right (FeeFilter 1000)+  it "writes sendtxrcncl as version then salt" $+    hexOf (SendTxRcncl 1 0x0102030405060708) `shouldBe` "01000000" <> "0807060504030201"+  it "reads sendtxrcncl" $+    parse (hex ("01000000" <> "0807060504030201")) `shouldBe` Right (SendTxRcncl 1 0x0102030405060708)+  it "writes an alert as two byte strings" $+    hexOf (Alert (VarString "ab") (VarString "c")) `shouldBe` "026162" <> "0163"+  it "reads an alert" $+    parse (hex ("026162" <> "0163")) `shouldBe` Right (Alert (VarString "ab") (VarString "c"))++-- Reject ----------------------------------------------------------------------++rejectSpec :: Spec+rejectSpec = describe "reject" $ do+  it "uses the code bytes of BIP 61" $+    map hexOf [RejectMalformed, RejectInvalid, RejectObsolete, RejectDuplicate, RejectNonStandard, RejectDust, RejectInsufficientFee, RejectCheckpoint]+      `shouldBe` ["01", "10", "11", "12", "40", "41", "42", "43"]+  it "reads every code" $+    forM_ [RejectMalformed, RejectInvalid, RejectObsolete, RejectDuplicate, RejectNonStandard, RejectDust, RejectInsufficientFee, RejectCheckpoint] $ \c ->+      parse (runPutS (serialize c)) `shouldBe` Right c+  it "refuses an unknown code" $+    (parse (hex "02") :: Either String RejectCode) `failsWith` "Invalid code"+  it "builds a reject without a hash" $+    (reject MCTx RejectDust "dust").extra `shouldBe` Nothing+  it "writes the command, code and reason, and no hash when there is none" $+    hexOf (reject MCTx RejectInvalid "bad") `shouldBe` "027478" <> "10" <> "03626164"+  it "writes the hash after the reason" $+    hexOf (Reject MCBlock RejectCheckpoint (VarString "") (Just h1))+      `shouldBe` "05626c6f636b" <> "43" <> "00" <> h1Hex+  it "reads a reject without a hash" $+    parse (hex ("027478" <> "10" <> "03626164")) `shouldBe` Right (reject MCTx RejectInvalid "bad")+  it "reads a reject with a hash" $+    parse (hex ("05626c6f636b" <> "43" <> "00" <> h1Hex))+      `shouldBe` Right (Reject MCBlock RejectCheckpoint (VarString "") (Just h1))+  it "refuses extra data that is not a hash" $+    (parse (hex ("027478" <> "10" <> "00" <> "0102030405")) :: Either String Reject) `failsWith` "too few bytes"++-- Message framing -------------------------------------------------------------++framingSpec :: Spec+framingSpec = describe "message framing" $ do+  it "writes a header as big-endian magic, padded command, little-endian length and checksum" $+    encodeHex (runPutS (putMessage btc MVerAck)) `shouldBe` verackMessageHex+  it "writes the payload after the header" $+    encodeHex (runPutS (putMessage btc (MPing (Ping 1)))) `shouldBe` pingMessageHex+  it "uses the magic of the network" $+    T.take 8 (encodeHex (runPutS (putMessage bch MVerAck))) `shouldBe` "e3e1f3e8"+  it "reads a header" $+    fmap (\h -> (h.magic, h.cmd, h.size)) (parse (hex verackMessageHex) :: Either String MessageHeader)+      `shouldBe` Right (0xf9beb4d9, MCVerAck, 0)+  it "writes a header back as it was" $+    fmap hexOf (parse (hex verackMessageHex) :: Either String MessageHeader) `shouldBe` Right verackMessageHex+  it "reads the verack message of the protocol documentation" $+    runGetS (getMessage btc) (hex verackMessageHex) `shouldBe` Right MVerAck+  it "reads the version message of the protocol documentation" $+    runGetS (getMessage btc) (hex versionMessageHex) `shouldBe` Right (MVersion wikiVersion)+  it "reads the addr message of the protocol documentation" $+    runGetS (getMessage btc) (hex addrMessageHex)+      `shouldBe` Right (MAddr (Addr [(1292899810, NetworkAddress 1 ipv4Host)]))+  it "refuses a message of another network" $+    runGetS (getMessage btcTest) (hex pingMessageHex) `failsWith` "Invalid network magic"+  it "refuses a message whose checksum does not match its payload" $+    runGetS (getMessage btc) (B.take 24 (hex pingMessageHex) <> hex "0200000000000000")+      `failsWith` "Invalid message checksum"+  it "refuses a length above maxMessageSize before reading the payload" $+    -- 4000001 is one above the limit of btc, written little-endian+    runGetS (getMessage btc) (hex ("f9beb4d9" <> "70696e670000000000000000" <> "01093d00" <> "5df6e0e2"))+      `failsWith` "larger than the limit"+  it "reads one message and leaves the next one in the stream" $+    runGetS ((,) <$> getMessage btc <*> getMessage btc) (hex (pingMessageHex <> verackMessageHex))+      `shouldBe` Right (MPing (Ping 1), MVerAck)+  it "hands the payload parser only the declared bytes" $+    -- a ping declared as 4 bytes, followed by 4 bytes that belong to nothing+    let payload = hex "01000000"+        msg = hex ("f9beb4d9" <> "70696e670000000000000000" <> "04000000") <> runPutS (serialize (checkSum32 payload)) <> payload <> hex "deadbeef"+     in runGetS (getMessage btc) msg `failsWith` "too few bytes"+  it "accepts a payload on a message type that carries none" $+    let payload = hex "00"+        msg = hex ("f9beb4d9" <> "76657261636b000000000000" <> "01000000") <> runPutS (serialize (checkSum32 payload)) <> payload+     in runGetS (getMessage btc) msg `shouldBe` Right MVerAck+  it "refuses a message type that needs a payload without one" $+    runGetS (getMessage btc) (hex ("f9beb4d9" <> "70696e670000000000000000" <> "00000000" <> "5df6e0e2"))+      `failsWith` "too few bytes"+  it "keeps the command and payload of an unknown message" $+    let payload = hex "616263"+        msg = hex ("f9beb4d9" <> "666f6f000000000000000000" <> "03000000") <> runPutS (serialize (checkSum32 payload)) <> payload+     in do+          runGetS (getMessage btc) msg `shouldBe` Right (MOther "foo" "abc")+          runPutS (putMessage btc (MOther "foo" "abc")) `shouldBe` msg+  it "gives each message its command" $ do+    msgType MVerAck `shouldBe` MCVerAck+    msgType (MPing (Ping 1)) `shouldBe` MCPing+    msgType MWtxidRelay `shouldBe` MCWtxidRelay+    msgType MSendAddrV2 `shouldBe` MCSendAddrV2+    msgType (MOther "foo" "") `shouldBe` MCOther "foo"++-- Bloom filters ---------------------------------------------------------------++bloomSpec :: Ctx -> Spec+bloomSpec ctx = describe "bloom filters" $ do+  describe "test vectors of Bitcoin Core" $ do+    it "bloom filter vector 1" bloomFilter1+    it "bloom filter vector 2" bloomFilter2+    it "bloom filter vector 3" $ bloomFilter3 ctx+  describe "bloomCreate" $ do+    it "sizes the filter for the elements and false positive rate" $ do+      -- 3 elements at 1%: 3 bytes and 5 hash functions, as in the vectors+      let f = bloomCreate 3 0.01 0 BloomUpdateAll+      S.length f.array `shouldBe` 3+      f.functions `shouldBe` 5+      -- 20000 elements at 0.1%: 20000 * 6.9078 / 0.48045 = 287552 bits,+      -- 35943 bytes; 35943 * 8 / 20000 * 0.6931 = 9.97, so 9 functions+      let g = bloomCreate 20000 0.001 0 BloomUpdateAll+      S.length g.array `shouldBe` 35943+      g.functions `shouldBe` 9+    it "caps the size at 36000 bytes and the hash functions at 50" $ do+      let f = bloomCreate 100000 0.01 0 BloomUpdateAll+      S.length f.array `shouldBe` 36000+      -- 36000 * 8 / 100000 * 0.6931 = 1.99, so 1 function+      f.functions `shouldBe` 1+      -- 1 element at 1e-20: 46.05 / 0.48045 = 95.85 bits, 11 bytes;+      -- 11 * 8 * 0.6931 = 60.99, capped at 50 functions+      let g = bloomCreate 1 1e-20 0 BloomUpdateAll+      S.length g.array `shouldBe` 11+      g.functions `shouldBe` 50+    it "gives zero hash functions when the elements outnumber the capped size" $+      -- 36000 * 8 / 1000000 * 0.6931 = 0.19+      (bloomCreate 1000000 0.01 0 BloomUpdateAll).functions `shouldBe` 0+    it "makes an empty filter for zero elements" $ do+      let f = bloomCreate 0 0.01 0 BloomUpdateAll+      S.length f.array `shouldBe` 0+      f.functions `shouldBe` 0+    it "keeps the tweak and flags" $ do+      let f = bloomCreate 3 0.01 7 BloomUpdateP2PubKeyOnly+      f.tweak `shouldBe` 7+      f.flags `shouldBe` BloomUpdateP2PubKeyOnly+  describe "bloomContains and bloomInsert" $ do+    it "does not match data that was not inserted" $+      bloomContains (bloomCreate 3 0.01 0 BloomUpdateAll) "abc" `shouldBe` False+    it "matches everything with no hash functions" $+      bloomContains (BloomFilter (S.replicate 10 0xaa) 0 0 BloomUpdateAll) "abc" `shouldBe` True+    it "changes nothing when inserting with no hash functions" $+      bloomInsert (BloomFilter (S.replicate 2 0) 0 0 BloomUpdateAll) "abc"+        `shouldBe` BloomFilter (S.replicate 2 0) 0 0 BloomUpdateAll+    it "matches everything with an empty filter" $+      bloomContains (BloomFilter S.empty 5 0 BloomUpdateAll) "abc" `shouldBe` True+    it "changes nothing when inserting into an empty filter" $+      bloomInsert (BloomFilter S.empty 5 0 BloomUpdateAll) "abc"+        `shouldBe` BloomFilter S.empty 5 0 BloomUpdateAll+    it "matches everything when every bit is set" $+      bloomContains (BloomFilter (S.replicate 3 0xff) 5 0 BloomUpdateAll) "abc" `shouldBe` True+  describe "serialization" $ do+    it "writes the bytes with their count, then functions, tweak and flags" $+      -- the first vector of Bitcoin Core after inserting its three elements+      hexOf (BloomFilter (S.fromList [0x61, 0x4e, 0x9b]) 5 0 BloomUpdateAll)+        `shouldBe` "03614e9b" <> "05000000" <> "00000000" <> "01"+    it "reads a filter" $+      parse (hex ("03614e9b" <> "05000000" <> "00000000" <> "01"))+        `shouldBe` Right (BloomFilter (S.fromList [0x61, 0x4e, 0x9b]) 5 0 BloomUpdateAll)+    it "writes the flags as one byte" $+      map hexOf [BloomUpdateNone, BloomUpdateAll, BloomUpdateP2PubKeyOnly] `shouldBe` ["00", "01", "02"]+    it "reads only the two low bits of the flags, as Bitcoin Core does" $ do+      parse (hex "03") `shouldBe` Right BloomUpdateNone+      parse (hex "81") `shouldBe` Right BloomUpdateAll+      parse (hex "82") `shouldBe` Right BloomUpdateP2PubKeyOnly+    it "accepts the largest filter a peer may load" $+      let f = BloomFilter (S.replicate 36000 0x55) 50 0 BloomUpdateAll+       in parse (runPutS (serialize f)) `shouldBe` Right f+    it "refuses a filter of 36001 bytes" $+      (parse (runPutS (serialize (BloomFilter (S.replicate 36001 0) 1 0 BloomUpdateAll))) :: Either String BloomFilter)+        `failsWith` "too large"+    it "refuses a filter with 51 hash functions" $+      (parse (runPutS (serialize (BloomFilter (S.replicate 1 0) 51 0 BloomUpdateAll))) :: Either String BloomFilter)+        `failsWith` "too many"+    it "refuses a byte count that does not fit the input" $+      -- 2^64 - 1 bytes announced+      (parse (hex ("ffffffffffffffffff" <> "01000000" <> "00000000" <> "01")) :: Either String BloomFilter)+        `failsWith` "too large"+    it "writes filterload as the filter" $+      hexOf (FilterLoad (BloomFilter (S.fromList [0x61]) 1 0 BloomUpdateNone)) `shouldBe` "0161" <> "01000000" <> "00000000" <> "00"+    it "writes filteradd as the element with its length" $+      hexOf (FilterAdd "abc") `shouldBe` "03616263"+    it "accepts a filteradd element of 520 bytes" $+      parse (hex "fd0802" <> B.replicate 520 0x11) `shouldBe` Right (FilterAdd (B.replicate 520 0x11))+    it "refuses a filteradd element of 521 bytes" $+      (parse (hex "fd0902" <> B.replicate 521 0x11) :: Either String FilterAdd) `failsWith` "too long"+  describe "limits" $ do+    it "knows the limits of BIP 37" $ do+      maxBloomSize `shouldBe` 36000+      maxHashFuncs `shouldBe` 50+      maxFilterAddSize `shouldBe` 520+    it "isBloomValid accepts the limits and refuses one more" $ do+      isBloomValid (BloomFilter (S.replicate 36000 0) 50 0 BloomUpdateAll) `shouldBe` True+      isBloomValid (BloomFilter (S.replicate 36001 0) 50 0 BloomUpdateAll) `shouldBe` False+      isBloomValid (BloomFilter (S.replicate 36000 0) 51 0 BloomUpdateAll) `shouldBe` False+    it "isBloomEmpty is true for all-zero bytes" $ do+      isBloomEmpty (BloomFilter (S.fromList [0, 0]) 1 0 BloomUpdateAll) `shouldBe` True+      isBloomEmpty (BloomFilter (S.fromList [0, 1]) 1 0 BloomUpdateAll) `shouldBe` False+    it "isBloomFull is true for all-0xff bytes" $ do+      isBloomFull (BloomFilter (S.fromList [0xff, 0xff]) 1 0 BloomUpdateAll) `shouldBe` True+      isBloomFull (BloomFilter (S.fromList [0xff, 0xfe]) 1 0 BloomUpdateAll) `shouldBe` False+    it "acceptsFilters looks at the NODE_BLOOM bit" $ do+      acceptsFilters 4 `shouldBe` True+      acceptsFilters 5 `shouldBe` True+      acceptsFilters 1 `shouldBe` False+      acceptsFilters 0 `shouldBe` False+  describe "scriptPushData" $ do+    it "lists the data pushed by a pay-to-pubkey-hash script" $+      scriptPushData p2pkhScript `shouldBe` [p2pkhHash]+    it "lists nothing for a script without pushes" $+      scriptPushData (hex "51") `shouldBe` []+    it "lists every push of an OP_RETURN script" $+      scriptPushData (hex "6a" <> hex "0111" <> hex "022233") `shouldBe` [hex "11", hex "2233"]+    it "leaves out empty pushes" $+      scriptPushData (hex "00" <> hex "0111") `shouldBe` [hex "11"]+    it "keeps the pushes before a truncated one" $+      -- a two-byte push, then OP_PUSHDATA1 with no length byte+      scriptPushData (hex "02aabb" <> hex "4c") `shouldBe` [hex "aabb"]+  describe "bloomRelevantUpdate" $ do+    it "inserts the outpoint of a matching output under BloomUpdateAll" relevantOutputUpdated+    it "gives Nothing for a transaction with no matching output" irrelevantOutputNotUpdated+    it "leaves the filter as it is for a matching pay-to-pubkey-hash output under BloomUpdateP2PubKeyOnly" $ do+      let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateP2PubKeyOnly) p2pkhHash+      bloomRelevantUpdate ctx bf (txPaying [p2pkhScript]) `shouldBe` Just bf+    it "inserts the outpoint of a matching pay-to-pubkey output under BloomUpdateP2PubKeyOnly" $ do+      let k = fromJust $ fromWif btc "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"+          p = derivePublicKey ctx k+          bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateP2PubKeyOnly) (marshal ctx p)+          tx = txPaying [marshal ctx (PayPK p)]+      fmap (`bloomContains` outpointBytes tx 0) (bloomRelevantUpdate ctx bf tx) `shouldBe` Just True+    it "leaves the filter as it is under BloomUpdateNone" $ do+      let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateNone) p2pkhHash+      bloomRelevantUpdate ctx bf (txPaying [p2pkhScript]) `shouldBe` Just bf+    it "scans a non-standard output script without failing" $ do+      let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateAll) p2pkhHash+      bloomRelevantUpdate ctx bf (txPaying [hex "51"]) `shouldBe` Nothing+    it "matches a push inside a non-standard output script" $ do+      let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateAll) p2pkhHash+          -- push the hash, drop it, then OP_TRUE+          tx = txPaying [hex "14" <> p2pkhHash <> hex "7551"]+      fmap (`bloomContains` outpointBytes tx 0) (bloomRelevantUpdate ctx bf tx) `shouldBe` Just True+    it "still finds the matching output next to a non-standard one" $ do+      let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateAll) p2pkhHash+          tx = txPaying [hex "51", p2pkhScript]+      fmap (`bloomContains` outpointBytes tx 1) (bloomRelevantUpdate ctx bf tx) `shouldBe` Just True+    it "matches every output with an empty filter and changes nothing" $ do+      let bf = BloomFilter S.empty 0 0 BloomUpdateAll+      bloomRelevantUpdate ctx bf (txPaying [hex "51"]) `shouldBe` Just bf+    it "does not insert the outpoint of an output that does not match" $ do+      let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateAll) p2pkhHash+          tx = txPaying [p2pkhScript, hex "76a914" <> B.replicate 20 0x42 <> hex "88ac"]+      fmap (`bloomContains` outpointBytes tx 1) (bloomRelevantUpdate ctx bf tx) `shouldBe` Just False++bloomFilter :: Word32 -> Text -> Expectation+bloomFilter n x = do+  bloomContains f1 v1 `shouldBe` True+  bloomContains f1 v2 `shouldBe` False+  bloomContains f3 v3 `shouldBe` True+  bloomContains f4 v4 `shouldBe` True+  runPutS (serialize f4) `shouldBe` bs+  where+    f0 = bloomCreate 3 0.01 n BloomUpdateAll+    f1 = bloomInsert f0 v1+    f3 = bloomInsert f1 v3+    f4 = bloomInsert f3 v4+    v1 = hex "99108ad8ed9bb6274d3980bab5a85c048f0950c8"+    v2 = hex "19108ad8ed9bb6274d3980bab5a85c048f0950c8"+    v3 = hex "b5a2c786d9ef4658287ced5914b37a1b4aa32eee"+    v4 = hex "b9300670b4c5366e95b2699e8b18bc75e5f729c5"+    bs = hex x++bloomFilter1 :: Expectation+bloomFilter1 = bloomFilter 0 "03614e9b050000000000000001"++bloomFilter2 :: Expectation+bloomFilter2 = bloomFilter 2147483649 "03ce4299050000000100008001"++bloomFilter3 :: Ctx -> Expectation+bloomFilter3 ctx =+  runPutS (serialize f2) `shouldBe` bs+  where+    f0 = bloomCreate 2 0.001 0 BloomUpdateAll+    f1 = bloomInsert f0 $ marshal ctx p+    f2 = bloomInsert f1 $ runPutS $ serialize $ fromJust $ addressHash160 $ pubKeyAddr ctx p+    k = fromJust $ fromWif btc "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"+    p = derivePublicKey ctx k+    bs = hex "038fc16b080000000000000001"++-- | The first output of 'relevantTx' pays the hash in the filter. Its+-- outpoint, which 'spendRelevantTx' spends with its first input, must be in+-- the updated filter. The second output pays somewhere else and its outpoint+-- must not be.+relevantOutputUpdated :: Expectation+relevantOutputUpdated = do+  bloomContains bf2 (outpoint <> hex "00000000") `shouldBe` True+  bloomContains bf2 (outpoint <> hex "01000000") `shouldBe` False+  where+    bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll+    bf1 = bloomInsert bf0 p2pkhHash+    bf2 = fromJust $ bloomRelevantUpdate undefined' bf1 relevantTx+    -- txid 57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9+    -- in internal byte order+    outpoint = hex "e925f2a4505198733a27dd4c78df09ad91879e46dd21b3b7aa4dad324f90dc57"+    undefined' = error "bloomRelevantUpdate must not need the context for pay-to-pubkey-hash under BloomUpdateAll"++irrelevantOutputNotUpdated :: Expectation+irrelevantOutputNotUpdated = bloomRelevantUpdate undefined' bf1 unrelatedTx `shouldBe` Nothing+  where+    bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll+    bf1 = bloomInsert bf0 p2pkhHash+    undefined' = error "bloomRelevantUpdate must not need the context when nothing matches"++-- Random transaction (57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9)+relevantTx :: Tx+relevantTx =+  Tx+    { version = 1,+      inputs =+        [ TxIn+            { outpoint = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1,+              script = fromJust $ decodeHex "473044022032fc8eef299b7e94b9a986a6aa2dcb9733ab804bef80df995e443b9c1f8c604202203335df7a2e2b4789451cdb4b2b05a786a81c51519eb6a567fd6fe8cd7b2d33fe014104272502dc63a512dad1473cb82a71be9baf4f4303abd1ff6028fc8a78e1f3aec1218907119dec14f07354850758ff0948e88a904fa411c4df7d5444414ec64ad6",+              sequence = 4294967295+            }+        ],+      outputs =+        [ TxOut {value = 100000000, script = fromJust $ decodeHex "76a91403f47604ea2736334151081e13265b4fe38e6fa888ac"},+          TxOut {value = 107980000, script = fromJust $ decodeHex "76a91481cc186a2f4a69f633ed4bf10ef4a78be13effdd88ac"}+        ],+      witness = [],+      locktime = 0+    }++-- This random transaction is unrelated to the others+unrelatedTx :: Tx+unrelatedTx =+  Tx+    { version = 1,+      inputs =+        [ TxIn+            { outpoint = OutPoint "3ec3a71431c68e5d978a5fb4a0a1081d8bee8384d8aa4c06b1fbaf9413e2214f" 20,+              script = fromJust $ decodeHex "483045022100ec9c202c9d3140b973aca9d7f21a82138aa4cfa43fddc5419098ac5e26a6f152022010848fd688f290ae010fb5cb493410caa03145fc12445900ec1ad2bde33aecd9012102c7445e72d723f99a0064526c28269d07f47c8fd81531a94a8d3bf5ebd5e23ef1",+              sequence = 4294967295+            }+        ],+      outputs =+        [ TxOut {value = 12600000, script = fromJust $ decodeHex "76a9148fef3b7051de8cc44e966159e7ea37f4520187e888ac"}+        ],+      witness = [],+      locktime = 0+    }++-- Compact blocks --------------------------------------------------------------++compactBlockSpec :: Spec+compactBlockSpec = describe "compact blocks" $ do+  it "writes sendcmpct as a flag byte and a little-endian version" $+    hexOf (SendCompact True 2) `shouldBe` "01" <> "0200000000000000"+  it "reads sendcmpct" $+    parse (hex ("00" <> "0100000000000000")) `shouldBe` Right (SendCompact False 1)+  it "limits short ids to six bytes" $+    maxShortId `shouldBe` 0xffffffffffff+  it "writes getblocktxn positions as the distance from the previous one minus one" $+    hexOf (GetBlockTxn bh1 [0, 1, 5]) `shouldBe` bh1Hex <> "03" <> "00" <> "00" <> "03"+  it "reads getblocktxn positions" $+    parse (hex (bh1Hex <> "03" <> "00" <> "00" <> "03")) `shouldBe` Right (GetBlockTxn bh1 [0, 1, 5])+  it "reads a first position that is not zero" $+    parse (hex (bh1Hex <> "02" <> "07" <> "00")) `shouldBe` Right (GetBlockTxn bh1 [7, 8])+  it "refuses positions that do not increase" $+    evaluate (runPutS (serialize (GetBlockTxn bh1 [1, 1]))) `shouldThrow` anyErrorCall+  it "refuses a position beyond 32 bits" $+    -- 0 then a distance of 0xffffffff, which lands on 2^32+    (parse (hex (bh1Hex <> "02" <> "00" <> "feffffffff")) :: Either String GetBlockTxn)+      `failsWith` "does not fit 32 bits"+  it "writes a compact block as header, nonce, short ids and prefilled transactions" $+    hexOf (CompactBlock btc.genesisHeader 0x0102030405060708 [1, maxShortId] [PrefilledTx 0 (txPaying [p2pkhScript])])+      `shouldBe` genesisHeaderHex+        <> "0807060504030201"+        <> "02"+        <> "010000000000"+        <> "ffffffffffff"+        <> "01"+        <> "00"+        <> oneInputTxHex+  it "reads a compact block" $+    parse (hex (genesisHeaderHex <> "0807060504030201" <> "02" <> "010000000000" <> "ffffffffffff" <> "01" <> "00" <> oneInputTxHex))+      `shouldBe` Right (CompactBlock btc.genesisHeader 0x0102030405060708 [1, maxShortId] [PrefilledTx 0 (txPaying [p2pkhScript])])+  it "writes prefilled positions as distances too" $+    let tx = txPaying [p2pkhScript]+     in hexOf (CompactBlock btc.genesisHeader 0 [] [PrefilledTx 0 tx, PrefilledTx 2 tx])+          `shouldBe` genesisHeaderHex <> "0000000000000000" <> "00" <> "02" <> "00" <> oneInputTxHex <> "01" <> oneInputTxHex+  it "refuses a short id wider than six bytes" $+    evaluate (runPutS (serialize (CompactBlock btc.genesisHeader 0 [maxShortId + 1] []))) `shouldThrow` anyErrorCall+  it "writes blocktxn as the block hash and the transactions" $+    hexOf (BlockTxn bh1 [txPaying [p2pkhScript]]) `shouldBe` bh1Hex <> "01" <> oneInputTxHex+  it "reads blocktxn" $+    parse (hex (bh1Hex <> "01" <> oneInputTxHex)) `shouldBe` Right (BlockTxn bh1 [txPaying [p2pkhScript]])++-- Block filters ---------------------------------------------------------------++blockFilterSpec :: Spec+blockFilterSpec = describe "block filters" $ do+  it "numbers the basic filter type zero" $+    basicFilterType `shouldBe` 0+  it "writes getcfilters as type, little-endian start height and stop hash" $+    hexOf (GetCFilters 0 1000 bh1) `shouldBe` "00" <> "e8030000" <> bh1Hex+  it "reads getcfilters" $+    parse (hex ("00" <> "e8030000" <> bh1Hex)) `shouldBe` Right (GetCFilters 0 1000 bh1)+  it "writes cfilter as type, block hash and the filter with its length" $+    hexOf (CFilter 0 bh1 (hex "abcd")) `shouldBe` "00" <> bh1Hex <> "02abcd"+  it "reads cfilter" $+    parse (hex ("00" <> bh1Hex <> "02abcd")) `shouldBe` Right (CFilter 0 bh1 (hex "abcd"))+  it "writes getcfheaders like getcfilters" $+    hexOf (GetCFHeaders basicFilterType 2000 bh1) `shouldBe` "00" <> "d0070000" <> bh1Hex+  it "reads getcfheaders" $+    parse (hex ("00" <> "d0070000" <> bh1Hex)) `shouldBe` Right (GetCFHeaders 0 2000 bh1)+  it "writes cfheaders as type, stop hash, previous header and the counted hashes" $+    hexOf (CFHeaders 0 bh1 h1 [h2, h3]) `shouldBe` "00" <> bh1Hex <> h1Hex <> "02" <> h2Hex <> h3Hex+  it "reads cfheaders" $+    parse (hex ("00" <> bh1Hex <> h1Hex <> "02" <> h2Hex <> h3Hex)) `shouldBe` Right (CFHeaders 0 bh1 h1 [h2, h3])+  it "writes getcfcheckpt as type and stop hash" $+    hexOf (GetCFCheckpt 0 bh1) `shouldBe` "00" <> bh1Hex+  it "reads getcfcheckpt" $+    parse (hex ("00" <> bh1Hex)) `shouldBe` Right (GetCFCheckpt 0 bh1)+  it "writes cfcheckpt as type, stop hash and the counted headers" $+    hexOf (CFCheckpt 0 bh1 [h1]) `shouldBe` "00" <> bh1Hex <> "01" <> h1Hex+  it "reads cfcheckpt" $+    parse (hex ("00" <> bh1Hex <> "01" <> h1Hex)) `shouldBe` Right (CFCheckpt 0 bh1 [h1])++-- Constants -------------------------------------------------------------------++constantsSpec :: Spec+constantsSpec = describe "network constants" $ do+  it "lists eight networks with distinct names and magics" $ do+    map (.name) allNets `shouldBe` ["btc", "bch", "btctest", "btctest4", "bchtest4", "bchtest", "btcreg", "bchreg"]+    length (nub (map (.magic) allNets)) `shouldBe` 8+  it "finds every network by name" $+    forM_ allNets $ \n -> netByName n.name `shouldBe` Just n+  it "finds nothing for an unknown name" $+    netByName "ltc" `shouldBe` Nothing+  it "uses the magic bytes of each network" $+    map (.magic) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+      `shouldBe` [0xf9beb4d9, 0x0b110907, 0x1c163f28, 0xfabfb5da, 0xe3e1f3e8, 0xf4e5f3f4, 0xe2b7daaf, 0xdab5bffa]+  it "uses the default port of each network" $+    map (.defaultPort) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+      `shouldBe` [8333, 18333, 48333, 18444, 8333, 18333, 28333, 18444]+  it "has the genesis block of each network" $+    map (headerHash . (.genesisHeader)) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+      `shouldBe` [ "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",+                   "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",+                   "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043",+                   "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",+                   "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",+                   "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",+                   "000000001dd410c49a788668ce26751718cc797474d3152a5fc073dd44fd9f7b",+                   "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"+                 ]+  it "limits messages to 4 MB on bitcoin and twice the block size on bitcoin cash" $+    map (.maxMessageSize) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+      `shouldBe` [4000000, 4000000, 4000000, 4000000, 64000000, 64000000, 4000000, 64000000]+  it "knows the block size of each network" $+    map (.maxBlockSize) [btc, bch, bchTest, bchTest4, bchRegTest]+      `shouldBe` [1000000, 32000000, 32000000, 2000000, 32000000]+  it "activates BIP34, BIP65 and BIP66 at height 1 on bitcoin regtest, as Bitcoin Core does" $ do+    btcRegTest.bip34Height `shouldBe` 1+    btcRegTest.bip34Hash `shouldBe` Nothing+    btcRegTest.bip65Height `shouldBe` 1+    btcRegTest.bip66Height `shouldBe` 1+  it "keeps the activation heights of Bitcoin Cash Node on bitcoin cash regtest" $ do+    bchRegTest.bip34Height `shouldBe` 100000000+    bchRegTest.bip34Hash `shouldBe` Nothing+    bchRegTest.bip65Height `shouldBe` 1351+    bchRegTest.bip66Height `shouldBe` 1251+  it "pins the BIP34 block on mainnet" $ do+    btc.bip34Height `shouldBe` 227931+    btc.bip34Hash `shouldBe` Just "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"+    btc.bip65Height `shouldBe` 388381+    btc.bip66Height `shouldBe` 363725+  it "pins block 1 of testnet4" $ do+    btcTest4.bip34Height `shouldBe` 1+    btcTest4.bip34Hash `shouldBe` Just "0000000012982b6d5f621229286b880e909984df669c2afabb102ce311b13f28"+  it "uses the BIP44 coin type of each network" $+    map (.bip44Coin) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+      `shouldBe` [0, 1, 1, 1, 145, 1, 1, 1]+  it "lists the Bitcoin Forks seed for bitcoin cash" $+    bch.seeds `shouldSatisfy` elem "seed-bch.bitcoinforks.org"+  it "numbers the service bits as Bitcoin Core does" $ do+    nodeNone `shouldBe` 0+    nodeNetwork `shouldBe` 1+    nodeGetUTXO `shouldBe` 2+    nodeBloom `shouldBe` 4+    nodeWitness `shouldBe` 8+    nodeXThin `shouldBe` 16+    nodeCompactFilters `shouldBe` 64+    nodeNetworkLimited `shouldBe` 1024+    nodeP2PV2 `shouldBe` 2048
test/Haskoin/ScriptSpec.hs view
@@ -5,8 +5,11 @@  module Haskoin.ScriptSpec (spec) where +import Control.Exception (evaluate) import Control.Monad+import Crypto.Secp256k1 (Sig) import Data.Aeson as A+import Data.Aeson.Types as A (parseMaybe) import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.Bytes.Get@@ -19,6 +22,7 @@ import Data.String import Data.String.Conversions (cs) import Data.Text (Text)+import Data.Text qualified as T import Data.Word import Haskoin.Address import Haskoin.Crypto@@ -88,19 +92,729 @@ spec :: Spec spec = prepareContext $ \ctx -> do   testIdentity $ identityTests ctx+  describe "opcodes" opcodeSpec+  describe "push operations" pushSpec+  describe "small integer opcodes" smallIntSpec+  describe "standard outputs" $ outputSpec ctx+  describe "standard inputs" $ inputSpec ctx+  describe "sighash types" sigHashTypeSpec+  describe "sighash digests" $ digestSpec ctx+  describe "transaction signatures" $ txSignatureSpec ctx   describe "btc scripts" $ props btc ctx   describe "bch scripts" $ props bch ctx   describe "multi signatures" $     zipWithM_ (curry (mapMulSigVector ctx)) mulSigVectors [0 ..]   describe "signature decoding" $     zipWithM_ (curry (sigDecodeMap btc ctx)) scriptSigSignatures [0 ..]-  describe "SigHashFlag fromEnum/toEnum" $-    prop "fromEnum/toEnum" $-      forAll arbitrarySigHashFlag $-        \f -> toEnum (fromEnum f) `shouldBe` f   describe "Script vectors" $     it "Can encode script vectors" encodeScriptVector +{- Fixtures -}++hex :: Text -> ByteString+hex = fromJust . decodeHex++-- | The generator point of secp256k1 as a compressed public key. Its hash160+-- is 751e76e8199196d454941c45d1b3a323f1433bd6, the witness program of the+-- BIP 173 example address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4.+gKeyHex :: Text+gKeyHex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"++gKey :: Ctx -> PublicKey+gKey ctx = either error id $ unmarshal ctx $ hex gKeyHex++gHash :: Hash160+gHash = "751e76e8199196d454941c45d1b3a323f1433bd6"++-- | The two keys of the first BIP 67 test vector, in the order the vector+-- lists them before sorting.+bip67KeyA, bip67KeyB :: Text+bip67KeyA = "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8"+bip67KeyB = "02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f"++bip67Key :: Ctx -> Text -> PublicKey+bip67Key ctx = either error id . unmarshal ctx . hex++-- | The 2-of-2 script of the first BIP 67 test vector, keys sorted: 71 bytes.+bip67ScriptHex :: Text+bip67ScriptHex = "5221" <> bip67KeyB <> "21" <> bip67KeyA <> "52ae"++-- | A strict DER signature from the input of transaction+-- 1983a69265920c24f89aac81942b1a59f7eb30821a8b3fb258f88882b6336053, without+-- its hash type byte. It is 70 bytes, so with the hash type byte a push of it+-- starts with 0x47.+sigDerHex :: Text+sigDerHex =+  "304402205ca6249f43538908151fe67b26d020306c0e59fa206cf9f3ccf641f33357119d\+  \02206c82f244d04ac0a48024fb9cc246b66e58598acf206139bdb7b75a2941a2b1e4"++sigDer :: Ctx -> Sig+sigDer ctx = fromJust $ decodeStrictSig ctx $ hex sigDerHex++-- | The signature with SIGHASH_ALL, pushed in a script.+sigAllPushHex :: Text+sigAllPushHex = "47" <> sigDerHex <> "01"++-- | The unsigned transaction of the native P2WPKH example of BIP 143.+bip143Tx :: Tx+bip143Tx =+  "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f\+  \0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57\+  \b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85\+  \c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2\+  \f0167faa815988ac11000000"++-- | The scriptCode of the second input of that transaction: the P2PKH script+-- of the key that the P2WPKH output pays to.+bip143ScriptCode :: Script+bip143ScriptCode =+  either error id $+    runGetS deserialize $+      hex "76a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac"++-- | The value of the output that the second input spends: 6 BTC.+bip143Value :: Word64+bip143Value = 600000000++-- | The digest BIP 143 gives for that input with SIGHASH_ALL.+bip143Digest :: Hash256+bip143Digest = "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670"++-- | Opcodes by their byte, from the script specification.+namedOpcodes :: [(Word8, ScriptOp)]+namedOpcodes =+  [ (0x4f, OP_1NEGATE),+    (0x50, OP_RESERVED),+    (0x51, OP_1),+    (0x60, OP_16),+    (0x61, OP_NOP),+    (0x62, OP_VER),+    (0x63, OP_IF),+    (0x64, OP_NOTIF),+    (0x67, OP_ELSE),+    (0x68, OP_ENDIF),+    (0x69, OP_VERIFY),+    (0x6a, OP_RETURN),+    (0x6b, OP_TOALTSTACK),+    (0x6c, OP_FROMALTSTACK),+    (0x73, OP_IFDUP),+    (0x74, OP_DEPTH),+    (0x75, OP_DROP),+    (0x76, OP_DUP),+    (0x7c, OP_SWAP),+    (0x7e, OP_CAT),+    (0x82, OP_SIZE),+    (0x87, OP_EQUAL),+    (0x88, OP_EQUALVERIFY),+    (0x8b, OP_1ADD),+    (0x93, OP_ADD),+    (0x94, OP_SUB),+    (0x9c, OP_NUMEQUAL),+    (0xa5, OP_WITHIN),+    (0xa6, OP_RIPEMD160),+    (0xa7, OP_SHA1),+    (0xa8, OP_SHA256),+    (0xa9, OP_HASH160),+    (0xaa, OP_HASH256),+    (0xab, OP_CODESEPARATOR),+    (0xac, OP_CHECKSIG),+    (0xad, OP_CHECKSIGVERIFY),+    (0xae, OP_CHECKMULTISIG),+    (0xaf, OP_CHECKMULTISIGVERIFY),+    (0xb0, OP_NOP1),+    (0xb1, OP_CHECKLOCKTIMEVERIFY),+    (0xb2, OP_CHECKSEQUENCEVERIFY),+    (0xb3, OP_NOP4),+    (0xb9, OP_NOP10),+    (0xba, OP_CHECKDATASIG),+    (0xbb, OP_CHECKDATASIGVERIFY),+    (0xbc, OP_REVERSEBYTES),+    (0xbd, OP_INVALIDOPCODE 0xbd),+    (0xfc, OP_INVALIDOPCODE 0xfc),+    (0xfd, OP_PUBKEYHASH),+    (0xfe, OP_PUBKEY),+    (0xff, OP_INVALIDOPCODE 0xff)+  ]++{- Opcodes and pushes -}++opcodeSpec :: Spec+opcodeSpec = do+  it "reads every byte from 0x4f to 0xff as one opcode that writes back to that byte" $+    forM_ [0x4f .. 0xff] $ \b ->+      (b, fmap (runPutS . serialize) (runGetS deserialize (B.singleton b) :: Either String ScriptOp))+        `shouldBe` (b, Right (B.singleton b))+  it "names the opcodes of the specification" $+    forM_ namedOpcodes $ \(b, op) -> do+      (b, runGetS deserialize (B.singleton b)) `shouldBe` (b, Right op)+      (b, runPutS (serialize op)) `shouldBe` (b, B.singleton b)+  it "reads the byte 0x00 as OP_0" $+    runGetS deserialize (B.singleton 0x00) `shouldBe` Right OP_0+  it "calls opcode 0xba OP_CHECKSIGADD too, as Tapscript does" $ do+    OP_CHECKSIGADD `shouldBe` OP_CHECKDATASIG+    runPutS (serialize OP_CHECKSIGADD) `shouldBe` B.singleton 0xba+    (case OP_CHECKDATASIG of OP_CHECKSIGADD -> True; _ -> False) `shouldBe` True+  it "shows and reads opcode names" $ do+    show OP_CHECKLOCKTIMEVERIFY `shouldBe` "OP_CHECKLOCKTIMEVERIFY"+    readMaybe "OP_DUP" `shouldBe` Just OP_DUP+    readMaybe "OP_FOO" `shouldBe` (Nothing :: Maybe ScriptOp)+  it "writes a script as the bytes of its opcodes and reads them back" $ do+    runPutS (serialize (Script [OP_1, OP_2, OP_ADD])) `shouldBe` hex "515293"+    runGetS deserialize (hex "515293") `shouldBe` Right (Script [OP_1, OP_2, OP_ADD])+    runGetS deserialize B.empty `shouldBe` Right (Script [])+  it "serializes a script to JSON as its hex and reads it back" $ do+    A.encode (Script [OP_1, OP_2, OP_ADD]) `shouldBe` "\"515293\""+    A.decode "\"515293\"" `shouldBe` Just (Script [OP_1, OP_2, OP_ADD])+    A.decode "\"zz\"" `shouldBe` (Nothing :: Maybe Script)+    A.decode "\"4c\"" `shouldBe` (Nothing :: Maybe Script)+    A.decode "515293" `shouldBe` (Nothing :: Maybe Script)+  describe "push data wire format" $ do+    it "writes data of 1 to 75 bytes after the byte of its length" $ do+      runPutS (serialize (OP_PUSHDATA (hex "aa") OPCODE)) `shouldBe` hex "01aa"+      runPutS (serialize (OP_PUSHDATA (B.replicate 75 0xcc) OPCODE))+        `shouldBe` B.singleton 0x4b <> B.replicate 75 0xcc+    it "writes data of 76 to 255 bytes after 0x4c and one length byte" $ do+      runPutS (serialize (OP_PUSHDATA (B.replicate 76 0xcc) OPDATA1))+        `shouldBe` hex "4c4c" <> B.replicate 76 0xcc+      runPutS (serialize (OP_PUSHDATA (B.replicate 255 0xcc) OPDATA1))+        `shouldBe` hex "4cff" <> B.replicate 255 0xcc+    it "writes data of 256 to 65535 bytes after 0x4d and two length bytes, little endian" $ do+      runPutS (serialize (OP_PUSHDATA (B.replicate 256 0xcc) OPDATA2))+        `shouldBe` hex "4d0001" <> B.replicate 256 0xcc+      runPutS (serialize (OP_PUSHDATA (B.replicate 65535 0xcc) OPDATA2))+        `shouldBe` hex "4dffff" <> B.replicate 65535 0xcc+    it "writes data of 65536 bytes or more after 0x4e and four length bytes, little endian" $+      runPutS (serialize (OP_PUSHDATA (B.replicate 65536 0xcc) OPDATA4))+        `shouldBe` hex "4e00000100" <> B.replicate 65536 0xcc+    it "reads each push type back with its payload" $ do+      runGetS deserialize (hex "01aa") `shouldBe` Right (OP_PUSHDATA (hex "aa") OPCODE)+      runGetS deserialize (hex "4c4c" <> B.replicate 76 0xcc)+        `shouldBe` Right (OP_PUSHDATA (B.replicate 76 0xcc) OPDATA1)+      runGetS deserialize (hex "4d0001" <> B.replicate 256 0xcc)+        `shouldBe` Right (OP_PUSHDATA (B.replicate 256 0xcc) OPDATA2)+      runGetS deserialize (hex "4e00000100" <> B.replicate 65536 0xcc)+        `shouldBe` Right (OP_PUSHDATA (B.replicate 65536 0xcc) OPDATA4)+    it "keeps a push that uses a longer type than its data needs" $ do+      runGetS deserialize (hex "4c01aa") `shouldBe` Right (OP_PUSHDATA (hex "aa") OPDATA1)+      runGetS deserialize (hex "4d0100aa") `shouldBe` Right (OP_PUSHDATA (hex "aa") OPDATA2)+      runGetS deserialize (hex "4e01000000aa") `shouldBe` Right (OP_PUSHDATA (hex "aa") OPDATA4)+      runGetS deserialize (hex "4c00") `shouldBe` Right (OP_PUSHDATA "" OPDATA1)+      runPutS (serialize (OP_PUSHDATA (hex "aa") OPDATA1)) `shouldBe` hex "4c01aa"+      runPutS (serialize (OP_PUSHDATA (hex "aa") OPDATA2)) `shouldBe` hex "4d0100aa"+      runPutS (serialize (OP_PUSHDATA (hex "aa") OPDATA4)) `shouldBe` hex "4e01000000aa"+    it "fails to read a push whose data is missing" $ do+      runGetS deserialize (hex "4c") `shouldSatisfy` (isLeft :: Either String ScriptOp -> Bool)+      runGetS deserialize (hex "02aa") `shouldSatisfy` (isLeft :: Either String ScriptOp -> Bool)+      runGetS deserialize (hex "4d0500aa") `shouldSatisfy` (isLeft :: Either String ScriptOp -> Bool)+      runGetS deserialize (hex "4e01000000") `shouldSatisfy` (isLeft :: Either String ScriptOp -> Bool)+      runGetS deserialize (hex "514c") `shouldSatisfy` (isLeft :: Either String Script -> Bool)+    it "fails to write a push whose payload does not fit its type" $ do+      evaluate (runPutS (serialize (OP_PUSHDATA (B.replicate 76 0) OPCODE))) `shouldThrow` anyErrorCall+      evaluate (runPutS (serialize (OP_PUSHDATA (B.replicate 256 0) OPDATA1))) `shouldThrow` anyErrorCall+      evaluate (runPutS (serialize (OP_PUSHDATA (B.replicate 65536 0) OPDATA2))) `shouldThrow` anyErrorCall++pushSpec :: Spec+pushSpec = do+  describe "opPushData" $ do+    it "uses OP_0 for the empty string" $+      opPushData "" `shouldBe` OP_0+    it "uses OP_1 to OP_16 for one byte from 1 to 16" $ do+      opPushData "\x01" `shouldBe` OP_1+      opPushData "\x10" `shouldBe` OP_16+    it "uses OP_1NEGATE for the byte 0x81" $+      opPushData "\x81" `shouldBe` OP_1NEGATE+    it "pushes any other single byte as data" $ do+      opPushData "\x00" `shouldBe` OP_PUSHDATA "\x00" OPCODE+      opPushData "\x11" `shouldBe` OP_PUSHDATA "\x11" OPCODE+      opPushData "\x80" `shouldBe` OP_PUSHDATA "\x80" OPCODE+    it "picks the smallest push type for the length of the data" $ do+      opPushData (B.replicate 75 0) `shouldBe` OP_PUSHDATA (B.replicate 75 0) OPCODE+      opPushData (B.replicate 76 0) `shouldBe` OP_PUSHDATA (B.replicate 76 0) OPDATA1+      opPushData (B.replicate 255 0) `shouldBe` OP_PUSHDATA (B.replicate 255 0) OPDATA1+      opPushData (B.replicate 256 0) `shouldBe` OP_PUSHDATA (B.replicate 256 0) OPDATA2+      opPushData (B.replicate 65535 0) `shouldBe` OP_PUSHDATA (B.replicate 65535 0) OPDATA2+      opPushData (B.replicate 65536 0) `shouldBe` OP_PUSHDATA (B.replicate 65536 0) OPDATA4+    prop "makes a minimal push that pushes the data" $+      forAll arbitraryBS $ \bs ->+        let op = opPushData bs+         in (isMinimalPush op, pushOpData op) `shouldBe` (True, Just bs)+  describe "pushOpData" $ do+    it "gives the data that a push opcode puts on the stack" $ do+      pushOpData OP_0 `shouldBe` Just ""+      pushOpData OP_1 `shouldBe` Just "\x01"+      pushOpData OP_16 `shouldBe` Just "\x10"+      pushOpData OP_1NEGATE `shouldBe` Just "\x81"+      pushOpData (OP_PUSHDATA "\xaa\xbb" OPDATA2) `shouldBe` Just "\xaa\xbb"+    it "gives nothing for an opcode that pushes nothing" $ do+      pushOpData OP_RESERVED `shouldBe` Nothing+      pushOpData OP_DUP `shouldBe` Nothing+      pushOpData OP_PUBKEY `shouldBe` Nothing+      pushOpData (OP_INVALIDOPCODE 0xff) `shouldBe` Nothing+  describe "isPushOp" $ do+    it "is true for the opcodes that push data" $+      forM_ [OP_0, OP_1NEGATE, OP_1, OP_16, OP_PUSHDATA "" OPDATA1, OP_PUSHDATA "\xaa" OPCODE] $ \op ->+        (op, isPushOp op) `shouldBe` (op, True)+    it "is false for every other opcode" $+      forM_ [OP_RESERVED, OP_NOP, OP_DUP, OP_CHECKSIG, OP_PUBKEYHASH, OP_INVALIDOPCODE 0xff] $ \op ->+        (op, isPushOp op) `shouldBe` (op, False)+  describe "isMinimalPush" $ do+    it "is true for the shortest encoding of the data" $+      forM_+        [ OP_0,+          OP_1,+          OP_16,+          OP_1NEGATE,+          OP_PUSHDATA "\x11" OPCODE,+          OP_PUSHDATA "\x00" OPCODE,+          OP_PUSHDATA (B.replicate 75 0) OPCODE,+          OP_PUSHDATA (B.replicate 76 0) OPDATA1,+          OP_PUSHDATA (B.replicate 256 0) OPDATA2,+          OP_PUSHDATA (B.replicate 65536 0) OPDATA4+        ]+        $ \op -> (B.length (runPutS (serialize op)), isMinimalPush op) `shouldSatisfy` snd+    it "is false for a longer encoding of the same data" $+      forM_+        [ OP_PUSHDATA "" OPCODE,+          OP_PUSHDATA "\x01" OPCODE,+          OP_PUSHDATA "\x81" OPCODE,+          OP_PUSHDATA (B.replicate 75 0) OPDATA1,+          OP_PUSHDATA (B.replicate 255 0) OPDATA2,+          OP_PUSHDATA (B.replicate 256 0) OPDATA4+        ]+        $ \op -> (B.length (runPutS (serialize op)), isMinimalPush op) `shouldSatisfy` (not . snd)+    it "is false for an opcode that pushes nothing" $+      isMinimalPush OP_DUP `shouldBe` False++smallIntSpec :: Spec+smallIntSpec = do+  it "maps 1 to 16 to OP_1 to OP_16" $ do+    intToScriptOp 1 `shouldBe` Right OP_1+    intToScriptOp 16 `shouldBe` Right OP_16+    scriptOpToInt OP_1 `shouldBe` Right 1+    scriptOpToInt OP_16 `shouldBe` Right 16+  it "fails for an integer outside 1 to 16" $ do+    intToScriptOp 0 `shouldSatisfy` isLeft+    intToScriptOp 17 `shouldSatisfy` isLeft+    intToScriptOp (-1) `shouldSatisfy` isLeft+  it "fails for an opcode that is not OP_1 to OP_16" $ do+    scriptOpToInt OP_0 `shouldSatisfy` isLeft+    scriptOpToInt OP_1NEGATE `shouldSatisfy` isLeft+    scriptOpToInt (OP_PUSHDATA "\x01" OPCODE) `shouldSatisfy` isLeft+    scriptOpToInt (OP_PUSHDATA (B.replicate 100 0) OPCODE) `shouldSatisfy` isLeft+  prop "has intToScriptOp . scriptOpToInt identity" $+    forAll arbitraryIntScriptOp $ \i ->+      (intToScriptOp =<< scriptOpToInt i) `shouldBe` Right i++{- Standard outputs -}++-- | Standard output scripts with their decoded form: name, hex, value.+standardOutputs :: Ctx -> [(String, Text, ScriptOutput)]+standardOutputs ctx =+  [ ("P2PK", "21" <> gKeyHex <> "ac", PayPK g),+    ("P2PKH", "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac", PayPKHash gHash),+    -- The P2SH-P2WPKH example of BIP 143.+    ( "P2SH",+      "a9144733f37cf4db86fbc2efed2500b4f4e49f31202387",+      PayScriptHash "4733f37cf4db86fbc2efed2500b4f4e49f312023"+    ),+    ( "P2SH32",+      "aa20" <> thirtyTwo <> "87",+      PayScript32Hash "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"+    ),+    -- The BIP 173 example addresses.+    ("P2WPKH", "0014751e76e8199196d454941c45d1b3a323f1433bd6", PayWitnessPKHash gHash),+    ( "P2WSH",+      "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",+      PayWitnessScriptHash "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"+    ),+    -- The first output of the BIP 341 wallet test vectors.+    ( "P2TR",+      "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",+      PayWitness 1 (hex "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343")+    ),+    ("witness v16 of 40 bytes", "6028" <> T.replicate 40 "01", PayWitness 16 (B.replicate 40 1)),+    ("witness v1 of 2 bytes", "51020102", PayWitness 1 "\x01\x02"),+    -- The first BIP 67 test vector.+    ("2-of-2 multisig", bip67ScriptHex, PayMulSig [bip67Key ctx bip67KeyB, bip67Key ctx bip67KeyA] 2),+    -- 17 and 20 are one-byte pushes, as Bitcoin Core writes counts above 16.+    ( "17-of-20 multisig",+      "0111" <> T.concat (replicate 20 ("21" <> gKeyHex)) <> "0114ae",+      PayMulSig (replicate 20 g) 17+    ),+    ("OP_RETURN alone", "6a", DataCarrier []),+    ("OP_RETURN with 36 bytes", "6a24" <> T.replicate 36 "ab", DataCarrier [B.replicate 36 0xab]),+    ("OP_RETURN with two pushes", "6a021122023344", DataCarrier ["\x11\x22", "\x33\x44"]),+    ("OP_RETURN with an empty push", "6a00", DataCarrier [""]),+    ("OP_RETURN with OP_1", "6a51", DataCarrier ["\x01"]),+    ("OP_RETURN with OP_1NEGATE", "6a4f", DataCarrier ["\x81"])+  ]+  where+    g = gKey ctx+    thirtyTwo = "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"++-- | Scripts that look like a standard output but are not one: name, hex.+nonStandardOutputs :: [(String, Text)]+nonStandardOutputs =+  [ -- Not witness programs by consensus: the program is not a direct push.+    ("P2WPKH with OP_PUSHDATA1", "004c14" <> twenty),+    ("P2WSH with OP_PUSHDATA1", "004c20" <> thirtyTwo),+    ("P2TR with OP_PUSHDATA1", "514c20" <> thirtyTwo),+    ("P2TR with OP_PUSHDATA2", "514d2000" <> thirtyTwo),+    ("witness v0 of 25 bytes with OP_PUSHDATA1", "004c19" <> T.replicate 25 "01"),+    -- Not pay-to-script-hash by consensus.+    ("P2SH with OP_PUSHDATA1", "a94c14" <> twenty <> "87"),+    ("P2SH32 with OP_PUSHDATA1", "aa4c20" <> thirtyTwo <> "87"),+    -- Non-standard in Bitcoin Core.+    ("P2PKH with OP_PUSHDATA1", "76a94c14" <> twenty <> "88ac"),+    ("P2PK with OP_PUSHDATA1", "4c21" <> gKeyHex <> "ac"),+    -- Wrong lengths.+    ("witness v0 of 25 bytes", "0019" <> T.replicate 25 "01"),+    ("witness v1 of 41 bytes", "5129" <> T.replicate 41 "01"),+    ("witness v1 of 1 byte", "510101"),+    ("P2PKH with a 19-byte hash", "76a913" <> T.replicate 19 "ab" <> "88ac"),+    ("P2SH with a 21-byte hash", "a915" <> T.replicate 21 "ab" <> "87"),+    -- A key must fill its push.+    ("P2PK with 32 bytes after a compressed key", "41" <> gKeyHex <> thirtyTwo <> "ac"),+    ("multisig with 32 bytes after a compressed key", "5141" <> gKeyHex <> thirtyTwo <> "51ae"),+    ("P2PK with a 32-byte key", "20" <> thirtyTwo <> "ac"),+    -- Multisig counts.+    ("multisig with a count of 0", "0021" <> gKeyHex <> "51ae"),+    ("multisig with OP_1NEGATE as count", "4f21" <> gKeyHex <> "51ae"),+    ("multisig with 1 as a byte push", "010121" <> gKeyHex <> "0101ae"),+    ("multisig with 21 keys", "0115" <> T.concat (replicate 21 ("21" <> gKeyHex)) <> "0115ae"),+    ("multisig requiring more signatures than keys", "5221" <> gKeyHex <> "51ae"),+    ("multisig whose key count is wrong", "5121" <> gKeyHex <> "52ae"),+    ("multisig ending in OP_CHECKMULTISIGVERIFY", "5121" <> gKeyHex <> "51af"),+    ("multisig without OP_CHECKMULTISIG", "5121" <> gKeyHex <> "51"),+    -- Not an output template at all.+    ("empty script", ""),+    ("OP_1", "51"),+    ("OP_RETURN followed by OP_DUP", "6a76"),+    ("OP_RETURN with a truncated push", "6a4c")+  ]+  where+    twenty = T.replicate 20 "ab"+    thirtyTwo = T.replicate 32 "cd"++outputSpec :: Ctx -> Spec+outputSpec ctx = do+  let g = gKey ctx+      decodeHexOutput = unmarshal ctx . hex :: Text -> Either String ScriptOutput+      encodeHexOutput = fmap (encodeHex . runPutS . serialize) . encodeOutput ctx+  it "decodes the standard output templates" $+    forM_ (standardOutputs ctx) $ \(name, h, out) ->+      (name, decodeHexOutput h) `shouldBe` (name, Right out)+  it "encodes the standard outputs to their templates" $+    forM_ (standardOutputs ctx) $ \(name, h, out) ->+      (name, encodeHexOutput out) `shouldBe` (name, Right h)+  it "rejects scripts that only look like a standard output" $+    forM_ nonStandardOutputs $ \(name, h) ->+      (name, decodeHexOutput h) `shouldSatisfy` (isLeft . snd)+  it "accepts a multisig key pushed with OP_PUSHDATA1, as the solver of Bitcoin Core does" $+    decodeHexOutput ("514c21" <> gKeyHex <> "51ae") `shouldBe` Right (PayMulSig [g] 1)+  it "has 20 as the most multisig keys" $+    maxMultisigKeys `shouldBe` 20+  it "refuses to encode a multisig that no script can express" $ do+    encodeOutput ctx (PayMulSig [] 0) `shouldSatisfy` isLeft+    encodeOutput ctx (PayMulSig [g] 0) `shouldSatisfy` isLeft+    encodeOutput ctx (PayMulSig [g] 2) `shouldSatisfy` isLeft+    encodeOutput ctx (PayMulSig (replicate 21 g) 1) `shouldSatisfy` isLeft+  it "refuses to encode a witness program outside the versions and lengths of BIP 141" $ do+    encodeOutput ctx (PayWitness 0 (B.replicate 20 1)) `shouldSatisfy` isLeft+    encodeOutput ctx (PayWitness 17 (B.replicate 20 1)) `shouldSatisfy` isLeft+    encodeOutput ctx (PayWitness 1 "\x01") `shouldSatisfy` isLeft+    encodeOutput ctx (PayWitness 1 (B.replicate 41 1)) `shouldSatisfy` isLeft+  it "fails with an error call when marshalling an output that cannot be encoded" $+    evaluate (marshal ctx (PayMulSig [] 0)) `shouldThrow` anyErrorCall+  it "marshals an output to JSON as the hex of its script" $ do+    marshalValue ctx (PayPKHash gHash) `shouldBe` A.String "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac"+    A.parseMaybe (unmarshalValue ctx) (A.String "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac")+      `shouldBe` Just (PayPKHash gHash)+    A.parseMaybe (unmarshalValue ctx) (A.String ("004c14" <> T.replicate 20 "ab"))+      `shouldBe` (Nothing :: Maybe ScriptOutput)+  describe "toP2SH and toP2WSH" $ do+    it "hashes the redeem script of the P2SH-P2WPKH example of BIP 143" $+      toP2SH (Script [OP_0, opPushData (hex "79091972186c449eb1ded22b78e40d009bdf0089")])+        `shouldBe` PayScriptHash "4733f37cf4db86fbc2efed2500b4f4e49f312023"+    it "hashes the witness script of the P2WSH example of BIP 143" $+      toP2WSH bip143WitnessScript+        `shouldBe` PayWitnessScriptHash "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"+  describe "sortMulSig" $ do+    it "sorts the keys of the first BIP 67 test vector" $ do+      let unsorted = PayMulSig [bip67Key ctx bip67KeyA, bip67Key ctx bip67KeyB] 2+      encodeHexOutput (sortMulSig ctx unsorted) `shouldBe` Right bip67ScriptHex+      addrToText btc (payToScriptAddress ctx (sortMulSig ctx unsorted))+        `shouldBe` Just "39bgKC7RFbpoCRbtD5KEdkYKtNyhpsNa3Z"+    it "leaves any other output unchanged" $+      sortMulSig ctx (PayPKHash gHash) `shouldBe` PayPKHash gHash+    prop "sorts the keys by their serialized bytes" $+      forAll (arbitraryMSOutput ctx) $ \out ->+        let keyList = map (marshal ctx) (sortMulSig ctx out).keys+         in keyList `shouldBe` sort keyList+  it "classifies every output type with exactly its own predicate" $ do+    let samples =+          [ PayPK g,+            PayPKHash gHash,+            PayMulSig [g] 1,+            PayScriptHash gHash,+            PayWitnessPKHash gHash,+            PayWitnessScriptHash "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",+            PayWitness 1 "\x01\x02",+            PayScript32Hash "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",+            DataCarrier []+          ]+        predicates =+          [ isPayPK,+            isPayPKHash,+            isPayMulSig,+            isPayScriptHash,+            isPayWitnessPKHash,+            isPayWitnessScriptHash,+            isPayWitness,+            isPayScript32Hash,+            isDataCarrier+          ]+    [[p s | s <- samples] | p <- predicates]+      `shouldBe` [[i == j | j <- [0 .. 8 :: Int]] | i <- [0 .. 8]]+  where+    bip143WitnessScript =+      either error id $+        runGetS deserialize $+          hex+            "21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aead\+            \ab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"++{- Standard inputs -}++-- | Standard input scripts with their decoded form: name, hex, value.+standardInputs :: Ctx -> [(String, Text, ScriptInput)]+standardInputs ctx =+  [ ("P2PK", sigAllPushHex, RegularInput (SpendPK sig)),+    ("P2PKH", sigAllPushHex <> "21" <> gKeyHex, RegularInput (SpendPKHash sig g)),+    ("2-of-n multisig", "00" <> sigAllPushHex <> sigAllPushHex, RegularInput (SpendMulSig [sig, sig])),+    ("multisig with one empty signature", "0000" <> sigAllPushHex, RegularInput (SpendMulSig [TxSignatureEmpty, sig])),+    ("P2PKH with an empty signature", "0021" <> gKeyHex, RegularInput (SpendPKHash TxSignatureEmpty g)),+    -- The P2SH-P2WPKH and P2SH-P2WSH examples of BIP 143.+    ( "P2SH-P2WPKH",+      "16001479091972186c449eb1ded22b78e40d009bdf0089",+      NestedWitnessInput (PayWitnessPKHash "79091972186c449eb1ded22b78e40d009bdf0089")+    ),+    ( "P2SH-P2WSH",+      "2200205d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0",+      NestedWitnessInput (PayWitnessScriptHash "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0")+    ),+    ("P2SH multisig without signatures yet", "0047" <> bip67ScriptHex, ScriptHashInput (SpendMulSig []) multisig),+    ("P2SH multisig with one signature", "00" <> sigAllPushHex <> "47" <> bip67ScriptHex, ScriptHashInput (SpendMulSig [sig]) multisig),+    ("P2SH P2PK", sigAllPushHex <> "2321" <> gKeyHex <> "ac", ScriptHashInput (SpendPK sig) (PayPK g)),+    ( "P2SH P2PKH",+      sigAllPushHex <> "21" <> gKeyHex <> "1976a914751e76e8199196d454941c45d1b3a323f1433bd688ac",+      ScriptHashInput (SpendPKHash sig g) (PayPKHash gHash)+    )+  ]+  where+    g = gKey ctx+    sig = TxSignature (sigDer ctx) sigHashAll+    multisig = PayMulSig [bip67Key ctx bip67KeyB, bip67Key ctx bip67KeyA] 2++-- | Scripts that are not a standard input: name, hex.+nonStandardInputs :: [(String, Text)]+nonStandardInputs =+  [ ("a push that is not a signature", "03010203"),+    ("a signature with a key followed by 32 bytes", sigAllPushHex <> "41" <> gKeyHex <> T.replicate 32 "ee"),+    ("a multisig redeem script without OP_0 before the signatures", sigAllPushHex <> "47" <> bip67ScriptHex),+    ("OP_0 before the signature of a P2PK redeem script", "00" <> sigAllPushHex <> "2321" <> gKeyHex <> "ac"),+    ("two signatures for a P2PKH redeem script", sigAllPushHex <> sigAllPushHex <> "1976a914751e76e8199196d454941c45d1b3a323f1433bd688ac"),+    ("a P2SH script as redeem script", sigAllPushHex <> "17a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"),+    ("a data carrier as redeem script", sigAllPushHex <> "026a00"),+    ("a signature before a nested witness program", sigAllPushHex <> "16001479091972186c449eb1ded22b78e40d009bdf0089"),+    ("a nested witness program of version 1", "22512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343"),+    ("a redeem script alone that is not a witness program", "2321" <> gKeyHex <> "ac"),+    ("a non-push opcode", "76")+  ]++inputSpec :: Ctx -> Spec+inputSpec ctx = do+  let g = gKey ctx+      decodeHexInput = unmarshal (btc, ctx) . hex :: Text -> Either String ScriptInput+      encodeHexInput = encodeHex . marshal (btc, ctx)+  it "decodes the standard input scripts" $+    forM_ (standardInputs ctx) $ \(name, h, inp) ->+      (name, decodeHexInput h) `shouldBe` (name, Right inp)+  it "encodes the standard inputs to their scripts" $+    forM_ (standardInputs ctx) $ \(name, h, inp) ->+      (name, encodeHexInput inp) `shouldBe` (name, h)+  it "rejects scripts that are not a standard input" $+    forM_ nonStandardInputs $ \(name, h) ->+      (name, decodeHexInput h) `shouldSatisfy` (isLeft . snd)+  it "reads a single empty push as a pay-to-public-key input with an empty signature" $ do+    -- The same script is a multisig input without signatures. Without the+    -- output script, the single-signature reading is taken.+    decodeInput btc ctx (Script [OP_0]) `shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))+    decodeInput btc ctx (Script [OP_0, OP_0]) `shouldBe` Right (RegularInput (SpendMulSig [TxSignatureEmpty]))+    decodeInput btc ctx (Script [OP_0, OP_0, OP_0, OP_0])+      `shouldBe` Right (RegularInput (SpendMulSig (replicate 3 TxSignatureEmpty)))+  it "writes an empty signature as OP_0" $ do+    encodeHexInput (RegularInput (SpendPK TxSignatureEmpty)) `shouldBe` "00"+    encodeHexInput (RegularInput (SpendMulSig [TxSignatureEmpty, TxSignatureEmpty])) `shouldBe` "000000"+    encodeHexInput (RegularInput (SpendMulSig [])) `shouldBe` "00"+  it "classifies every input type with exactly its own predicate" $ do+    let samples =+          [ RegularInput (SpendPK TxSignatureEmpty),+            RegularInput (SpendPKHash TxSignatureEmpty g),+            RegularInput (SpendMulSig []),+            ScriptHashInput (SpendPK TxSignatureEmpty) (PayPK g),+            NestedWitnessInput (PayWitnessPKHash gHash)+          ]+        predicates = [isSpendPK, isSpendPKHash, isSpendMulSig, isScriptHashInput, isNestedWitnessInput]+    [[p s | s <- samples] | p <- predicates]+      `shouldBe` [[i == j | j <- [0 .. 4 :: Int]] | i <- [0 .. 4]]++{- Sighash types -}++sigHashTypeSpec :: Spec+sigHashTypeSpec = do+  it "can correctly show" $ do+    show (0x00 :: SigHash) `shouldBe` "SigHash " <> show (0x00 :: Word32)+    show (0x01 :: SigHash) `shouldBe` "SigHash " <> show (0x01 :: Word32)+    show (0xff :: SigHash) `shouldBe` "SigHash " <> show (0xff :: Word32)+    show (0xabac3344 :: SigHash)+      `shouldBe` "SigHash "+        <> show (0xabac3344 :: Word32)+  it "can add a forkid" $ do+    0x00 `sigHashAddForkId` 0x00 `shouldBe` 0x00+    0xff `sigHashAddForkId` 0x00ffffff `shouldBe` 0xffffffff+    0xffff `sigHashAddForkId` 0x00aaaaaa `shouldBe` 0xaaaaaaff+    0xffff `sigHashAddForkId` 0xaaaaaaaa `shouldBe` 0xaaaaaaff+    0xffff `sigHashAddForkId` 0x00004444 `shouldBe` 0x004444ff+    0xff01 `sigHashAddForkId` 0x44440000 `shouldBe` 0x44000001+    0xff03 `sigHashAddForkId` 0x00550000 `shouldBe` 0x55000003+  it "can extract a forkid" $ do+    sigHashGetForkId 0x00000000 `shouldBe` 0x00000000+    sigHashGetForkId 0x80000000 `shouldBe` 0x00800000+    sigHashGetForkId 0xffffffff `shouldBe` 0x00ffffff+    sigHashGetForkId 0xabac3403 `shouldBe` 0x00abac34+  it "adds the fork id of the network, which is 0 on Bitcoin Cash" $ do+    sigHashAddNetworkId btc 0x01 `shouldBe` 0x01+    sigHashAddNetworkId bch 0x41 `shouldBe` 0x41+    sigHashAddNetworkId bch 0xaa0041 `shouldBe` 0x41+  it "can build some vectors" $ do+    sigHashAll `shouldBe` 0x01+    sigHashNone `shouldBe` 0x02+    sigHashSingle `shouldBe` 0x03+    setForkIdFlag sigHashAll `shouldBe` 0x41+    setAnyoneCanPay sigHashAll `shouldBe` 0x81+    setAnyoneCanPay (setForkIdFlag sigHashAll) `shouldBe` 0xc1+  it "can test flags" $ do+    hasForkIdFlag sigHashAll `shouldBe` False+    hasForkIdFlag (setForkIdFlag sigHashAll) `shouldBe` True+    anyoneCanPay sigHashAll `shouldBe` False+    anyoneCanPay (setAnyoneCanPay sigHashAll) `shouldBe` True+    isSigHashAll sigHashNone `shouldBe` False+    isSigHashAll sigHashAll `shouldBe` True+    isSigHashAll 0x81 `shouldBe` True+    isSigHashNone sigHashSingle `shouldBe` False+    isSigHashNone sigHashNone `shouldBe` True+    isSigHashNone 0xc2 `shouldBe` True+    isSigHashSingle sigHashAll `shouldBe` False+    isSigHashSingle sigHashSingle `shouldBe` True+    isSigHashSingle 0x43 `shouldBe` True+  it "knows the hash type bytes that IsDefinedHashtypeSignature of Bitcoin Core accepts" $ do+    forM_ [0x01, 0x02, 0x03, 0x81, 0x82, 0x83, 0x41, 0xc1, 0xc3] $ \sh ->+      (sh, isSigHashUnknown sh) `shouldBe` (sh, False)+    forM_ [0x00, 0x04, 0x05, 0x1f, 0x20, 0x21, 0x22, 0x40, 0x80, 0xc0, 0xff] $ \sh ->+      (sh, isSigHashUnknown sh) `shouldBe` (sh, True)+  it "ignores a fork id above the hash type byte" $+    isSigHashUnknown 0x00aa0041 `shouldBe` False+  it "gives the flags their values" $ do+    fromEnum SIGHASH_ALL `shouldBe` 0x01+    fromEnum SIGHASH_NONE `shouldBe` 0x02+    fromEnum SIGHASH_SINGLE `shouldBe` 0x03+    fromEnum SIGHASH_FORKID `shouldBe` 0x40+    fromEnum SIGHASH_ANYONECANPAY `shouldBe` 0x80+  prop "has toEnum . fromEnum identity on flags" $+    forAll arbitrarySigHashFlag $+      \f -> toEnum (fromEnum f) `shouldBe` f+  it "has no flag for other values" $+    evaluate (toEnum 0x05 :: SigHashFlag) `shouldThrow` anyErrorCall+  it "serializes to JSON as a number and reads only 32-bit values" $ do+    A.encode (0x81 :: SigHash) `shouldBe` "129"+    A.decode "129" `shouldBe` Just (0x81 :: SigHash)+    A.decode "-1" `shouldBe` (Nothing :: Maybe SigHash)+    A.decode "4294967296" `shouldBe` (Nothing :: Maybe SigHash)+    A.decode "1.5" `shouldBe` (Nothing :: Maybe SigHash)++{- Digests -}++digestSpec :: Ctx -> Spec+digestSpec ctx = do+  it "computes the BIP 143 digest of the P2WPKH example" $+    txSigHashForkId btc bip143Tx bip143ScriptCode bip143Value 1 sigHashAll+      `shouldBe` Right bip143Digest+  it "appends the hash type as four bytes, so other flags change the digest" $+    txSigHashForkId btc bip143Tx bip143ScriptCode bip143Value 1 (setAnyoneCanPay sigHashAll)+      `shouldNotBe` Right bip143Digest+  it "fails for an input index the transaction does not have" $ do+    txSigHash btc bip143Tx bip143ScriptCode bip143Value 2 sigHashAll `shouldSatisfy` isLeft+    txSigHash btc bip143Tx bip143ScriptCode bip143Value (-1) sigHashAll `shouldSatisfy` isLeft+    txSigHashForkId btc bip143Tx bip143ScriptCode bip143Value 2 sigHashAll `shouldSatisfy` isLeft+    txSigHashForkId btc bip143Tx bip143ScriptCode bip143Value (-1) sigHashAll `shouldSatisfy` isLeft+  it "removes OP_CODESEPARATOR from the script code of the legacy digest, as FindAndDelete does" $+    txSigHash btc bip143Tx (Script (OP_CODESEPARATOR : bip143ScriptCode.ops)) bip143Value 1 sigHashAll+      `shouldBe` txSigHash btc bip143Tx bip143ScriptCode bip143Value 1 sigHashAll+  it "keeps OP_CODESEPARATOR in the script code of the BIP 143 digest" $+    txSigHashForkId btc bip143Tx (Script (OP_CODESEPARATOR : bip143ScriptCode.ops)) bip143Value 1 sigHashAll+      `shouldNotBe` Right bip143Digest+  it "requires SIGHASH_FORKID on Bitcoin Cash" $+    txSigHash bch bip143Tx bip143ScriptCode bip143Value 1 sigHashAll `shouldSatisfy` isLeft+  it "uses the fork id digest on Bitcoin Cash" $ do+    txSigHash bch bip143Tx bip143ScriptCode bip143Value 1 (setForkIdFlag sigHashAll)+      `shouldBe` txSigHashForkId bch bip143Tx bip143ScriptCode bip143Value 1 (setForkIdFlag sigHashAll)+    -- The hash type 0x41 goes into the digest, so it differs from the+    -- Bitcoin digest of the same input with 0x01.+    txSigHash bch bip143Tx bip143ScriptCode bip143Value 1 (setForkIdFlag sigHashAll)+      `shouldNotBe` Right bip143Digest+  it "uses the legacy digest on Bitcoin even with the SIGHASH_FORKID flag" $+    txSigHash btc bip143Tx bip143ScriptCode bip143Value 1 (setForkIdFlag sigHashAll)+      `shouldNotBe` Right bip143Digest++{- Transaction signatures -}++txSignatureSpec :: Ctx -> Spec+txSignatureSpec ctx = do+  let sig = sigDer ctx+  it "writes the signature followed by its hash type byte" $ do+    encodeTxSig btc ctx (TxSignature sig sigHashAll) `shouldBe` hex (sigDerHex <> "01")+    encodeTxSig btc ctx (TxSignature sig 0x81) `shouldBe` hex (sigDerHex <> "81")+    encodeTxSig bch ctx (TxSignature sig 0x41) `shouldBe` hex (sigDerHex <> "41")+  it "writes an empty signature as the empty string" $+    encodeTxSig btc ctx TxSignatureEmpty `shouldBe` ""+  it "reads a signature with its hash type byte" $ do+    decodeTxSig btc ctx (hex (sigDerHex <> "01")) `shouldBe` Right (TxSignature sig 0x01)+    decodeTxSig btc ctx (hex (sigDerHex <> "82")) `shouldBe` Right (TxSignature sig 0x82)+    decodeTxSig bch ctx (hex (sigDerHex <> "c3")) `shouldBe` Right (TxSignature sig 0xc3)+  it "reads the empty string as an empty signature" $+    decodeTxSig btc ctx "" `shouldBe` Right TxSignatureEmpty+  it "requires the SIGHASH_FORKID flag exactly on networks with a fork id" $ do+    decodeTxSig btc ctx (hex (sigDerHex <> "41")) `shouldSatisfy` isLeft+    decodeTxSig btc ctx (hex (sigDerHex <> "c1")) `shouldSatisfy` isLeft+    decodeTxSig bch ctx (hex (sigDerHex <> "01")) `shouldSatisfy` isLeft+    decodeTxSig bch ctx (hex (sigDerHex <> "81")) `shouldSatisfy` isLeft+  it "rejects a hash type byte that no node accepts" $+    forM_ ["00", "04", "20", "21", "ff"] $ \sh ->+      (sh, decodeTxSig btc ctx (hex (sigDerHex <> sh))) `shouldSatisfy` (isLeft . snd)+  it "rejects bytes after the hash type byte" $+    decodeTxSig btc ctx (hex (sigDerHex <> "0101")) `shouldSatisfy` isLeft+  it "rejects a signature without a hash type byte" $+    decodeTxSig btc ctx (hex sigDerHex) `shouldSatisfy` isLeft+  it "rejects a signature that is not strict DER" $+    decodeTxSig btc ctx (hex ("3045022100" <> T.drop 8 sigDerHex <> "01")) `shouldSatisfy` isLeft+  it "marshals a signature to JSON as its hex" $ do+    marshalValue (btc, ctx) (TxSignature sig sigHashAll) `shouldBe` A.String (sigDerHex <> "01")+    A.parseMaybe (unmarshalValue (btc, ctx)) (A.String (sigDerHex <> "01")) `shouldBe` Just (TxSignature sig sigHashAll)+    A.parseMaybe (unmarshalValue (btc, ctx)) (A.String (sigDerHex <> "41")) `shouldBe` (Nothing :: Maybe TxSignature)++{- Per-network properties and vectors -}+ props :: Network -> Ctx -> Spec props net ctx = do   standardSpec net ctx@@ -113,33 +827,15 @@  standardSpec :: Network -> Ctx -> Spec standardSpec net ctx = do-  prop "has intToScriptOp . scriptOpToInt identity" $-    forAll arbitraryIntScriptOp $ \i ->-      intToScriptOp <$> scriptOpToInt i `shouldBe` Right i   prop "has decodeOutput . encodeOutput identity" $     forAll (arbitraryScriptOutput net ctx) $ \so ->-      decodeOutput ctx (encodeOutput ctx so) `shouldBe` Right so-  prop "has decodeInput . encodeOutput identity" $+      (decodeOutput ctx =<< encodeOutput ctx so) `shouldBe` Right so+  prop "has decodeInput . encodeInput identity" $     forAll (arbitraryScriptInput net ctx) $ \si ->       (decodeInput net ctx . encodeInput net ctx) si `shouldBe` Right si-  prop "can sort multisig scripts" $-    forAll (arbitraryMSOutput ctx) $ \out ->-      let keyList = map (marshal ctx) (sortMulSig ctx out).keys-          isSorted xs = xs == sort xs-       in keyList `shouldSatisfy` isSorted-  it "can decode inputs with empty signatures" $ do-    decodeInput net ctx (Script [OP_0])-      `shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))-    decodeInput net ctx (Script [opPushData ""])-      `shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))-    let Just sk = secKey (B.replicate 32 1)-        pk = derivePublicKey ctx (wrapSecKey True sk)-    decodeInput net ctx (Script [OP_0, opPushData $ marshal ctx pk])-      `shouldBe` Right (RegularInput (SpendPKHash TxSignatureEmpty pk))-    decodeInput net ctx (Script [OP_0, OP_0])-      `shouldBe` Right (RegularInput (SpendMulSig [TxSignatureEmpty]))-    decodeInput net ctx (Script [OP_0, OP_0, OP_0, OP_0])-      `shouldBe` Right (RegularInput (SpendMulSig $ replicate 3 TxSignatureEmpty))+  prop "gives every encodable output a script that decodes to it" $+    forAll (arbitraryScriptOutput net ctx) $ \so ->+      unmarshal ctx (marshal ctx so) `shouldBe` Right so  scriptSpec :: Network -> Ctx -> Spec scriptSpec net ctx =@@ -159,16 +855,12 @@       forM_ vectors $ \([val], siStr, soStr, flags, res, desc) ->         -- We can disable specific tests by adding a DISABLED flag in the data         unless ("DISABLED" `isInfixOf` flags) $ do-          let _strict =-                any-                  (`isInfixOf` flags)-                  ["DERSIG", "STRICTENC", "NULLDUMMY"]-              scriptSig = parseScript siStr+          let scriptSig = parseScript siStr               scriptPubKey = parseScript soStr               out = unmarshal ctx scriptPubKey-              tx = spendTx scriptPubKey 0 scriptSig               sat = val * 100000000-              ver o = verifyStdInput net ctx tx 0 o sat+              tx = spendTx scriptPubKey sat scriptSig+              ver o = verifyStdInput net ctx tx [(o, sat, (head tx.inputs).outpoint)] 0               valid = either (const False) ver out           assertBool desc $ if res == "OK" then valid else not valid @@ -194,7 +886,7 @@             scriptPubKey = parseScript soStr             out = unmarshal ctx scriptPubKey             tx = spendTx scriptPubKey val scriptSig-            ver o = verifyStdInput net ctx tx 0 o val+            ver o = verifyStdInput net ctx tx [(o, val, (head tx.inputs).outpoint)] 0             valid = either (const False) ver out         case res of           "OK" -> valid `shouldBe` True@@ -281,7 +973,7 @@             res =               eitherToMaybe . runGetS deserialize . B.reverse                 =<< decodeHex (cs resStr)-        Just (txSigHash net tx s 0 i sh) `shouldBe` res+        eitherToMaybe (txSigHash net tx s 0 i sh) `shouldBe` res  txSigHashForkIdSpec :: Network -> Spec txSigHashForkIdSpec net =@@ -306,72 +998,43 @@                 eitherToMaybe . runGetS deserialize =<< decodeHex (cs scpStr)             sh = fromIntegral shI             res = eitherToMaybe . runGetS deserialize =<< decodeHex (cs resStr)-        Just (txSigHashForkId net tx s val i sh) `shouldBe` res+        eitherToMaybe (txSigHashForkId net tx s val i sh) `shouldBe` res  sigHashSpec :: Network -> Ctx -> Spec sigHashSpec net ctx = do-  it "can correctly show" $ do-    show (0x00 :: SigHash) `shouldBe` "SigHash " <> show (0x00 :: Word32)-    show (0x01 :: SigHash) `shouldBe` "SigHash " <> show (0x01 :: Word32)-    show (0xff :: SigHash) `shouldBe` "SigHash " <> show (0xff :: Word32)-    show (0xabac3344 :: SigHash)-      `shouldBe` "SigHash "-        <> show (0xabac3344 :: Word32)-  it "can add a forkid" $ do-    0x00 `sigHashAddForkId` 0x00 `shouldBe` 0x00-    0xff `sigHashAddForkId` 0x00ffffff `shouldBe` 0xffffffff-    0xffff `sigHashAddForkId` 0x00aaaaaa `shouldBe` 0xaaaaaaff-    0xffff `sigHashAddForkId` 0xaaaaaaaa `shouldBe` 0xaaaaaaff-    0xffff `sigHashAddForkId` 0x00004444 `shouldBe` 0x004444ff-    0xff01 `sigHashAddForkId` 0x44440000 `shouldBe` 0x44000001-    0xff03 `sigHashAddForkId` 0x00550000 `shouldBe` 0x55000003-  it "can extract a forkid" $ do-    sigHashGetForkId 0x00000000 `shouldBe` 0x00000000-    sigHashGetForkId 0x80000000 `shouldBe` 0x00800000-    sigHashGetForkId 0xffffffff `shouldBe` 0x00ffffff-    sigHashGetForkId 0xabac3403 `shouldBe` 0x00abac34-  it "can build some vectors" $ do-    sigHashAll `shouldBe` 0x01-    sigHashNone `shouldBe` 0x02-    sigHashSingle `shouldBe` 0x03-    setForkIdFlag sigHashAll `shouldBe` 0x41-    setAnyoneCanPay sigHashAll `shouldBe` 0x81-    setAnyoneCanPay (setForkIdFlag sigHashAll) `shouldBe` 0xc1-  it "can test flags" $ do-    hasForkIdFlag sigHashAll `shouldBe` False-    hasForkIdFlag (setForkIdFlag sigHashAll) `shouldBe` True-    anyoneCanPay sigHashAll `shouldBe` False-    anyoneCanPay (setAnyoneCanPay sigHashAll) `shouldBe` True-    isSigHashAll sigHashNone `shouldBe` False-    isSigHashAll sigHashAll `shouldBe` True-    isSigHashNone sigHashSingle `shouldBe` False-    isSigHashNone sigHashNone `shouldBe` True-    isSigHashSingle sigHashAll `shouldBe` False-    isSigHashSingle sigHashSingle `shouldBe` True-    isSigHashUnknown sigHashAll `shouldBe` False-    isSigHashUnknown sigHashNone `shouldBe` False-    isSigHashUnknown sigHashSingle `shouldBe` False-    isSigHashUnknown 0x00 `shouldBe` True-    isSigHashUnknown 0x04 `shouldBe` True   it "can decodeTxSig . encode a TxSignature" $     property $       forAll (arbitraryTxSignature net ctx) $ \(_, _, ts) ->         let f = decodeTxSig net ctx . encodeTxSig net ctx          in f ts `shouldBe` Right ts-  it "can produce the sighash one" $-    property $-      forAll (arbitraryTx net ctx) $-        forAll arbitraryScript . testSigHashOne net+  if isNothing net.sigHashForkId+    then+      it "can produce the sighash one" $+        property $+          forAll (arbitraryTx net ctx) $+            forAll arbitraryScript . testSigHashOne net+    else+      it "never produces the sighash one, since the fork id digest has no such case" $+        property $+          forAll (arbitraryTx net ctx) $ \tx ->+            forAll arbitraryScript $ \s ->+              not (null tx.inputs) ==>+                txSigHash net tx s 0 (length tx.inputs - 1) (setForkIdFlag sigHashSingle)+                  `shouldSatisfy` \res -> isRight res && res /= Right sigHashOne +-- | The digest that the legacy algorithm gives for SIGHASH_SINGLE without a+-- matching output.+sigHashOne :: Hash256+sigHashOne = "0100000000000000000000000000000000000000000000000000000000000000"+ testSigHashOne :: Network -> Tx -> Script -> Word64 -> Bool -> Property testSigHashOne net tx s val acp =   not (null tx.inputs) ==>     if length tx.inputs > length tx.outputs-      then res `shouldBe` one-      else res `shouldNotBe` one+      then res `shouldBe` Right sigHashOne+      else res `shouldNotBe` Right sigHashOne   where     res = txSigHash net tx s val (length tx.inputs - 1) (f sigHashSingle)-    one = "0100000000000000000000000000000000000000000000000000000000000000"     f =       if acp         then setAnyoneCanPay
− test/Haskoin/Transaction/PartialSpec.hs
@@ -1,615 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE ImportQualifiedPost #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeApplications #-}--module Haskoin.Transaction.PartialSpec (spec) where--import Control.Monad ((<=<))-import Data.Aeson (FromJSON, parseJSON, withObject, (.:))-import Data.Bifunctor (first)-import Data.ByteString (ByteString)-import Data.ByteString.Base64 (decodeBase64Untyped)-import Data.Bytes.Get-import Data.Bytes.Put-import Data.Bytes.Serial-import Data.Either (fromRight, isLeft, isRight)-import Data.HashMap.Strict (fromList, singleton)-import Data.Maybe (fromJust, isJust)-import Data.Serialize-import Data.Text (Text)-import Data.Text qualified as Text-import Data.Text.Encoding (encodeUtf8)-import Haskoin.Address-import Haskoin.Crypto-import Haskoin.Network.Constants-import Haskoin.Network.Data-import Haskoin.Script-import Haskoin.Transaction-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Test.HUnit (Assertion, assertBool, assertEqual)-import Test.Hspec-import Test.QuickCheck--spec :: Spec-spec = prepareContext $ \ctx ->-  describe "PSBT unit tests" $ do-    it "encodes trivial PSBT" $-      (encodeHex . runPut . putPSBT ctx) trivialPSBT == trivialPSBTHex-    it "decodes trivial PSBT" $-      decodeHexPSBT ctx trivialPSBTHex == Right trivialPSBT-    it "encodes and decodes non-empty transactions" $-      (runGet (getPSBT ctx) . runPut . putPSBT ctx) nonEmptyTransactionPSBT == Right nonEmptyTransactionPSBT-    it "does not decode invalid BIP test vectors" $-      mapM_ (invalidVecTest ctx) invalidVec-    it "encodes valid BIP test vectors" $-      mapM_ (uncurry (encodeVecTest ctx)) validEncodeVec-    it "decodes valid BIP test vectors" $-      mapM_ (uncurry (decodeVecTest ctx)) $-        zip [1 ..] validVec-    it "decodes vector 2" $ vec2Test ctx-    it "decodes vector 3" $ vec3Test ctx-    it "decodes vector 4" $ vec4Test ctx-    it "decodes vector 5" $ vec5Test ctx-    it "decodes vector 6" $ vec6Test ctx-    it "signed and finalized P2PKH PSBTs verify" $-      property $-        forAll (arbitraryKeyPair ctx) $-          verifyNonWitnessPSBT btc ctx . unfinalizedPkhPSBT btc ctx-    it "signed and finalized multisig PSBTs verify" $-      property $-        forAll (arbitraryMultiSig ctx) $-          verifyNonWitnessPSBT btc ctx . unfinalizedMsPSBT btc ctx-    it "encodes and decodes PSBT with final witness script" $-      (fmap (encodeHex . runPut . putPSBT ctx) . decodeHexPSBT ctx) validVec7Hex == Right validVec7Hex-    it "handles complex PSBTs correctly" $ complexPsbtTest ctx-    it "calculates keys properly" $ psbtSignerTest ctx--vec2Test :: Ctx -> Assertion-vec2Test ctx = do-  psbt <- decodeHexPSBTM ctx "Cannot parse validVec2" validVec2Hex-  assertEqual "2 inputs" 2 (length psbt.inputs)-  assertEqual "2 outputs" 2 (length psbt.outputs)-  assertBool "final script sig" $ isJust (head psbt.inputs).finalScriptSig--  let rdmScript = fromJust (psbt.inputs !! 1).inputRedeemScript-  assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput ctx rdmScript) == Right True--  let scrptPubKey = witnessScriptPubKey ctx (psbt.inputs !! 1)-      rdmScriptP2SH = toP2SH rdmScript-  assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey-  assertEqual "expected redeem script" (expectedOut ctx) rdmScriptP2SH--  mapM_ (assertEqual "outputs are empty" emptyOutput) psbt.outputs--vec3Test :: Ctx -> Assertion-vec3Test ctx = do-  psbt <- decodeHexPSBTM ctx "Cannot parse validVec3" validVec3Hex-  assertEqual "1 input" 1 (length psbt.inputs)-  assertEqual "2 outputs" 2 (length psbt.outputs)-  let (txInput : _) = psbt.unsignedTransaction.inputs-      (firstInput : _) = psbt.inputs-      Just utx = firstInput.nonWitnessUtxo-      OutPoint prevHash prevVOut = txInput.outpoint-  assertEqual "txids of inputs match" prevHash (txHash utx)-  let prevOutputKey =-        fromRight (error "Could not decode key")-          . unmarshal ctx-          . (.script)-          $ utx.outputs !! fromIntegral prevVOut-  assertBool "p2pkh" $ isPayPKHash prevOutputKey-  assertEqual "sighash type" sigHashAll (fromJust firstInput.sigHashType)--vec4Test :: Ctx -> Assertion-vec4Test ctx = do-  psbt <- decodeHexPSBTM ctx "Cannot parse validVec4" validVec4Hex-  assertEqual "2 inputs" 2 (length psbt.inputs)-  assertEqual "2 outputs" 2 (length psbt.outputs)-  let (firstInput : _) = psbt.inputs-      (_ : secondInput : _) = psbt.inputs-  assertEqual "first input not final script sig" Nothing firstInput.finalScriptSig-  assertEqual "second input not final script sig" Nothing secondInput.finalScriptSig--  let Just rdmScript = secondInput.inputRedeemScript-  assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput ctx rdmScript) == Right True--  let scrptPubKey = witnessScriptPubKey ctx secondInput-      rdmScriptP2SH = toP2SH rdmScript-  assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey-  assertEqual "expected redeem script" (expectedOut ctx) rdmScriptP2SH--  assertBool "all non-empty outputs" $ emptyOutput `notElem` psbt.outputs--vec5Test :: Ctx -> Assertion-vec5Test ctx = do-  psbt <- decodeHexPSBTM ctx "Cannot parse validVec5" validVec5Hex-  assertEqual "Correctly decode PSBT" expectedPsbt psbt-  let (input : _) = psbt.inputs--  let Just rdmScript = input.inputRedeemScript-  assertBool "p2wsh" $ (isPayWitnessScriptHash <$> decodeOutput ctx rdmScript) == Right True--  let scrptPubKey = witnessScriptPubKey ctx input-      rdmScriptP2SH = toP2SH rdmScript-  assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey-  assertEqual "expected redeem script" expectedOut2 rdmScriptP2SH-  where-    expectedOut2 =-      fromRight (error "could not decode expected output")-        . unmarshal ctx-        . fromJust-        $ decodeHex "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"-    -- From the bitcoind decodepsbt rpc call-    expectedPsbt =-      PSBT-        { unsignedTransaction =-            Tx-              { version = 2,-                inputs =-                  [ TxIn-                      { outpoint =-                          OutPoint-                            { hash = "39bc5c3b33d66ce3d7852a7942331e3ec10f8ba50f225fc41fb5dfa523239a27",-                              index = 0-                            },-                        script = "",-                        sequence = 4294967295-                      }-                  ],-                outputs =-                  [ TxOut-                      { value = 199908000,-                        script = (fromJust . decodeHex) "76a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac"-                      }-                  ],-                witness = mempty,-                locktime = 0-              },-          globalUnknown = mempty,-          inputs =-            [ Input-                { nonWitnessUtxo = Nothing,-                  witnessUtxo =-                    Just-                      ( TxOut-                          { value = 199909013,-                            script = (fromJust . decodeHex) "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"-                          }-                      ),-                  partialSigs =-                    fromList-                      [ ( PublicKey-                            { point =-                                fromJust $-                                  importPubKey ctx-                                    =<< decodeHex-                                      "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",-                              compress = True-                            },-                          (fromJust . decodeHex) "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01"-                        )-                      ],-                  sigHashType = Nothing,-                  inputRedeemScript =-                    Just-                      . fromRight (error "vec5Test: Could not decode redeem script")-                      . decode-                      . fromJust-                      $ decodeHex "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681",-                  inputWitnessScript =-                    Just-                      . fromRight (error "vec5Test: Could not decode witness script")-                      . decode-                      . fromJust-                      $ decodeHex "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae",-                  inputHDKeypaths =-                    fromList-                      [ ( PublicKey-                            { point =-                                fromJust $-                                  importPubKey ctx-                                    =<< decodeHex-                                      "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",-                              compress = True-                            },-                          ("b4a6ba67", [hardIndex 0, hardIndex 0, hardIndex 4])-                        ),-                        ( PublicKey-                            { point =-                                fromJust $-                                  importPubKey ctx-                                    =<< decodeHex-                                      "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd",-                              compress = True-                            },-                          ("b4a6ba67", [hardIndex 0, hardIndex 0, hardIndex 5])-                        )-                      ],-                  finalScriptSig = Nothing,-                  finalScriptWitness = Nothing,-                  inputUnknown = mempty-                }-            ],-          outputs =-            [ Output-                { outputRedeemScript = Nothing,-                  outputWitnessScript = Nothing,-                  outputHDKeypaths = mempty,-                  outputUnknown = mempty-                }-            ]-        }-    hardIndex = (+ 2 ^ 31)--vec6Test :: Ctx -> Assertion-vec6Test ctx = do-  psbt <- decodeHexPSBTM ctx "Cannot parse validVec6" validVec6Hex-  assertEqual "1 input" 1 (length psbt.inputs)-  assertEqual "1 output" 1 (length psbt.outputs)--  let tx = psbt.unsignedTransaction-  assertEqual "correct transaction" "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115" (txHash tx)--  assertEqual "correct unknowns" expectedUnknowns (head psbt.inputs).inputUnknown-  where-    expectedUnknowns =-      UnknownMap $-        singleton-          (Key 0x0f (fromJust $ decodeHex "010203040506070809"))-          (fromJust $ decodeHex "0102030405060708090a0b0c0d0e0f")--complexPsbtTest :: Ctx -> Assertion-complexPsbtTest ctx = do-  complex <- readTestFileParser (parseComplexJSON ctx) "complex_psbt.json"--  let computedCombinedPsbt = mergeMany $ complexSignedPsbts complex-      expectedCombinedPsbt = stripRedundantUtxos $ complexCombinedPsbt complex-  assertEqual "combined psbt" computedCombinedPsbt (Just expectedCombinedPsbt)--  let computedCompletePsbt = complete ctx $ complexCombinedPsbt complex-      expectedCompletePsbt = complexCompletePsbt complex-  assertEqual "complete psbt" computedCompletePsbt expectedCompletePsbt--  let computedFinalTx = finalTransaction $ complexCompletePsbt complex-  assertEqual "final tx" computedFinalTx (complexFinalTx complex)-  where-    stripRedundantUtxos PSBT {..} = PSBT {inputs = stripRedundantUtxo <$> inputs, ..}-    stripRedundantUtxo input-      | Just {} <- input.witnessUtxo = input {nonWitnessUtxo = Nothing}-      | otherwise = input--psbtSignerTest :: Ctx -> Assertion-psbtSignerTest ctx = do-  assertEqual "recover explicit secret key" (Just theSecKey) (getSignerKey signer thePubKey Nothing)-  assertEqual-    "recover key for origin path"-    (Just originPathSecKey)-    (getSignerKey signer originPathPubKey (Just (rootFP, originKeyPath)))-  assertEqual-    "recover key for direct path"-    (Just directPathSecKey)-    (getSignerKey signer directPathPubKey (Just (keyFP, directPath)))-  where-    signer = secKeySigner ctx theSecKey <> xPrvSigner ctx xprv (Just origin)--    Just theSecKey = secKey "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"-    thePubKey = PublicKey {point = derivePubKey ctx theSecKey, compress = True}--    rootXPrv = makeXPrvKey "PSBTSignerTest"-    rootFP = xPubFP ctx $ deriveXPubKey ctx rootXPrv-    xprv = derivePath ctx keyPath rootXPrv-    keyFP = xPubFP ctx $ deriveXPubKey ctx xprv-    keyPath = Deriv :| 444-    origin = (rootFP, keyPath)--    originKeyPath = Deriv :| 444 :/ 0-    originPathSecKey = (derivePath ctx originKeyPath rootXPrv).key-    originPathPubKey = PublicKey {point = derivePubKey ctx originPathSecKey, compress = True}--    directPath = Deriv :/ 1-    directPathSecKey = (derivePath ctx directPath xprv).key-    directPathPubKey = PublicKey {point = derivePubKey ctx directPathSecKey, compress = True}--expectedOut :: Ctx -> ScriptOutput-expectedOut ctx =-  fromRight (error "could not decode expected output")-    . unmarshal ctx-    . fromJust-    $ decodeHex "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"--witnessScriptPubKey :: Ctx -> Input -> ScriptOutput-witnessScriptPubKey ctx =-  fromRight (error "could not decode witness utxo")-    . unmarshal ctx-    . (.script)-    . fromJust-    . (.witnessUtxo)--decodeHexPSBT :: Ctx -> Text -> Either String PSBT-decodeHexPSBT ctx = runGet (getPSBT ctx) . fromJust . decodeHex--decodeHexPSBTM :: (Monad m, MonadFail m) => Ctx -> String -> Text -> m PSBT-decodeHexPSBTM ctx errMsg = either (fail . (errMsg <>) . (": " <>)) return . decodeHexPSBT ctx--hexScript :: Text -> ByteString-hexScript =-  either (error "Could not decode script") encodeScript-    . runGetS deserialize-    . fromJust-    . decodeHex-  where-    encodeScript :: Script -> ByteString-    encodeScript = runPutS . serialize--invalidVecTest :: Ctx -> Text -> Assertion-invalidVecTest ctx = assertBool "invalid psbt" . isLeft . decodeHexPSBT ctx--decodeVecTest :: Ctx -> Int -> Text -> Assertion-decodeVecTest ctx i = assertBool (show i <> " decodes correctly") . isRight . decodeHexPSBT ctx--encodeVecTest :: Ctx -> PSBT -> Text -> Assertion-encodeVecTest ctx psbt hex =-  assertEqual-    "encodes correctly"-    ((runPut . putPSBT ctx) psbt)-    ((fromJust . decodeHex) hex)--trivialPSBT :: PSBT-trivialPSBT =-  PSBT-    { unsignedTransaction = Tx {version = 2, inputs = [], outputs = [], witness = [], locktime = 0},-      globalUnknown = UnknownMap mempty,-      inputs = [],-      outputs = []-    }--trivialPSBTHex :: Text-trivialPSBTHex = "70736274ff01000a0200000000000000000000"--nonEmptyTransactionPSBT :: PSBT-nonEmptyTransactionPSBT = emptyPSBT testTx1--verifyNonWitnessPSBT :: Network -> Ctx -> PSBT -> Bool-verifyNonWitnessPSBT net ctx psbt =-  verifyStdTx net ctx (finalTransaction (complete ctx psbt)) sigData-  where-    sigData = inputSigData =<< zip psbt.inputs psbt.unsignedTransaction.inputs-    decodeOutScript = fromRight (error "Could not parse output script") . unmarshal ctx-    inputSigData (input@Input {}, txInput@TxIn {}) =-      map-        (\(TxOut val script) -> (decodeOutScript script, val, txInput.outpoint))-        (fromJust input.nonWitnessUtxo).outputs--unfinalizedPkhPSBT :: Network -> Ctx -> (PrivateKey, PublicKey) -> PSBT-unfinalizedPkhPSBT net ctx (prvKey, pubKey) =-  let PSBT {..} = emptyPSBT currTx-   in PSBT {inputs = [input], ..}-  where-    input =-      emptyInput-        { nonWitnessUtxo = Just prevTx,-          partialSigs = singleton pubKey sig-        }-    currTx =-      unfinalizedTx (txHash prevTx)-    prevTx =-      testUtxo [prevOut]-    prevOutScript =-      fromJust $ addressToScript ctx (pubKeyAddr ctx pubKey)-    prevOut =-      TxOut-        { value = 200000000,-          script = runPutS (serialize prevOutScript)-        }-    h = txSigHash net currTx prevOutScript prevOut.value 0 sigHashAll-    sig =-      encodeTxSig net ctx $-        TxSignature (signHash ctx prvKey.key h) sigHashAll--arbitraryMultiSig :: Ctx -> Gen ([(PrivateKey, PublicKey)], Int)-arbitraryMultiSig ctx = do-  (m, n) <- arbitraryMSParam-  keys <- vectorOf n (arbitraryKeyPair ctx)-  return (keys, m)--unfinalizedMsPSBT :: Network -> Ctx -> ([(PrivateKey, PublicKey)], Int) -> PSBT-unfinalizedMsPSBT net ctx (keys, m) =-  let PSBT {..} = emptyPSBT currTx-   in PSBT {inputs = [input], ..}-  where-    input =-      emptyInput-        { nonWitnessUtxo = Just prevTx,-          partialSigs = sigs,-          inputRedeemScript = Just prevOutScript-        }-    currTx = unfinalizedTx (txHash prevTx)-    prevTx = testUtxo [prevOut]-    prevOutScript = encodeOutput ctx $ PayMulSig (map snd keys) m-    prevOut =-      TxOut-        { value = 200000000,-          script = marshal ctx (toP2SH prevOutScript)-        }-    h = txSigHash net currTx prevOutScript prevOut.value 0 sigHashAll-    sigs = fromList $ map sig keys-    sig (prvKey@PrivateKey {key}, pubKey) =-      let sh = signHash ctx key h-          ts = TxSignature sh sigHashAll-       in (pubKey, encodeTxSig net ctx ts)--unfinalizedTx :: TxHash -> Tx-unfinalizedTx prevHash =-  Tx-    { version = 2,-      inputs =-        [ TxIn-            { outpoint = OutPoint prevHash 0,-              script = "",-              sequence = 4294967294-            }-        ],-      outputs =-        [ TxOut {value = 99999699, script = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"},-          TxOut {value = 100000000, script = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"}-        ],-      witness = [],-      locktime = 1257139-    }--invalidVec :: [Text]-invalidVec =-  [ "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300",-    "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",-    "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000",-    "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",-    "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000",-    "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",-    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",-    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",-    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",-    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",-    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",-    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",-    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",-    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",-    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",-    "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",-    "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",-    "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00"-  ]--validEncodeVec :: [(PSBT, Text)]-validEncodeVec = [(validVec1, validVec1Hex)]--testTx1 :: Tx-testTx1 =-  Tx-    { version = 2,-      inputs =-        [ TxIn-            { outpoint = OutPoint "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126" 0,-              script = "",-              sequence = 4294967294-            }-        ],-      outputs =-        [ TxOut {value = 99999699, script = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"},-          TxOut {value = 100000000, script = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"}-        ],-      witness = [],-      locktime = 1257139-    }--testUtxo :: [TxOut] -> Tx-testUtxo prevOuts =-  Tx-    { version = 1,-      inputs =-        [ TxIn-            { outpoint = OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1,-              script = hexScript "160014be18d152a9b012039daf3da7de4f53349eecb985",-              sequence = 4294967295-            },-          TxIn-            { outpoint = OutPoint "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886" 1,-              script = hexScript "160014fe3e9ef1a745e974d902c4355943abcb34bd5353",-              sequence = 4294967295-            }-        ],-      outputs = prevOuts,-      witness =-        [ [ fromJust $ decodeHex "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01",-            fromJust $ decodeHex "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"-          ],-          [ fromJust $ decodeHex "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01",-            fromJust $ decodeHex "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"-          ]-        ],-      locktime = 0-    }--testUtxo1 :: Tx-testUtxo1 =-  testUtxo-    [ TxOut {value = 200000000, script = hexScript "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"},-      TxOut {value = 190303501938, script = hexScript "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"}-    ]--validVec1 :: PSBT-validVec1 =-  let PSBT {..} = emptyPSBT testTx1-   in PSBT {inputs = [emptyInput {nonWitnessUtxo = Just testUtxo1}], ..}--validVec :: [Text]-validVec = [validVec1Hex, validVec2Hex, validVec3Hex, validVec4Hex, validVec5Hex, validVec6Hex]--validVec1Hex :: Text-validVec1Hex = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000"--validVec2Hex :: Text-validVec2Hex = "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000"--validVec3Hex :: Text-validVec3Hex = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000"--validVec4Hex :: Text-validVec4Hex = "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000"--validVec5Hex :: Text-validVec5Hex = "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"--validVec6Hex :: Text-validVec6Hex = "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000"---- Example of a PSBT with a `finalWitnessScript`-validVec7Hex :: Text-validVec7Hex = "70736274ff0100520200000001815dd29e16fd2f567a040ce24f5337fb9cfd0c05bacd8890714a33edc7cbbc920000000000ffffffff0192f1052a01000000160014ef9ade26f63015d57f4ecdb268d1a9b8d6cd8872000000000001008402000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a010000001600145f4ffa19dbbe464561c50fc4d8d8ac0b41009dd20000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90000000001011f00f2052a010000001600145f4ffa19dbbe464561c50fc4d8d8ac0b41009dd201086b02473044022026a9f7afdb0128970bb3577e536ec3d3dc10c1e82650d11c9da1df9003b31d0202202258b11f962f12e0897c642cd6f38a0181db17197f3693a530c9431eb44dde7d0121033dc786e9628bb6c41c08fceb9b37458ad7a95e7e6b04e0bde45b6879398c3ac100220203a6affb58dda998a4ffdce652feb91038fdfc78c748ae687372e11292af8d312d101c4c5bfc00000080000000800100008000"--data ComplexPSBT = ComplexPSBT-  { complexSignedPsbts :: [PSBT],-    complexCombinedPsbt :: PSBT,-    complexCompletePsbt :: PSBT,-    complexFinalTx :: Tx-  }-  deriving (Eq, Show)--parseComplexJSON ctx = withObject "ComplexPSBT" $ \obj -> do-  ComplexPSBT-    <$> sequence-      [ psbtField "miner_psbt" obj,-        psbtField "p2pkh_psbt" obj,-        psbtField "p2sh_ms_1_psbt" obj,-        psbtField "p2sh_ms_2_psbt" obj,-        psbtField "p2sh_pk_psbt" obj,-        psbtField "p2sh_wsh_pk_psbt" obj,-        psbtField "p2sh_wsh_ms_1_psbt" obj,-        psbtField "p2sh_wsh_ms_2_psbt" obj,-        psbtField "p2wpkh_psbt" obj,-        psbtField "p2wsh_pk_psbt" obj,-        psbtField "p2wsh_ms_1_psbt" obj,-        psbtField "p2wsh_ms_2_psbt" obj-      ]-    <*> psbtField "combined_psbt" obj-    <*> psbtField "complete_psbt" obj-    <*> (obj .: "final_tx" >>= parseTx)-  where-    parseTx =-      either fail pure-        . ( decode-              <=< maybe (Left "hex") Right-                . decodeHex-          )-    parsePsbt =-      either fail pure-        . ( runGet (getPSBT ctx)-              <=< first Text.unpack . decodeBase64Untyped-          )-        . encodeUtf8-    psbtField fieldName obj =-      obj .: fieldName >>= parsePsbt
+ test/Haskoin/Transaction/PsbtSpec.hs view
@@ -0,0 +1,1234 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}++-- | The PSBT format of BIP 174, and its version 2 of BIP 370: the vectors+-- of both BIPs, the bytes of a PSBT and the refusals of what breaks the+-- spec, the fields of the spec, the roles: combining, signing, finalizing+-- and extracting, and the inputs and outputs a PSBT of version 2 takes.+module Haskoin.Transaction.PsbtSpec (spec) where++import Control.Monad (foldM, forM_)+import Data.Aeson ((.:), (.:?))+import Data.Aeson qualified as Json+import Data.ByteString (ByteString)+import Data.ByteString qualified as B+import Data.Either (isLeft)+import Data.List (isSuffixOf)+import Crypto.Hash (Digest, SHA256, digestFromByteString)+import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (serialize)+import Data.Map.Strict qualified as Map+import Data.Maybe (fromJust, fromMaybe, isJust, listToMaybe, mapMaybe)+import Data.Serialize qualified as S+import Data.Text (Text)+import Data.Text.Encoding (encodeUtf8)+import Data.Word (Word32, Word64)+import Haskoin.Address+import Haskoin.Crypto+import Haskoin.Network.Constants+import Haskoin.Network.Data+import Haskoin.Script+import Haskoin.Transaction+import Haskoin.Util+import Haskoin.Util.Arbitrary+import Test.Hspec+import Test.Hspec.QuickCheck (prop)+import Test.QuickCheck++{- The vectors of the BIP -}++-- The vectors live in data/psbt_bip174.json, copied from the BIP. Each has+-- the case the BIP gives, its hex, and its base64 when the BIP gives it.+data Vector = Vector+  { vecCase :: String,+    vecHex :: Text,+    vecBase64 :: Maybe Text+  }++instance Json.FromJSON Vector where+  parseJSON = Json.withObject "vector" $ \o -> Vector <$> o .: "case" <*> o .: "hex" <*> o .:? "base64"++-- | The invalid PSBTs, the valid ones, the PSBTs of the walk-through of the+-- roles, in the order of the BIP, and the transaction its extractor gives+data Vectors = Vectors+  { invalid :: [Vector],+    valid :: [Vector],+    roles :: [Vector],+    extracted :: Vector+  }++instance Json.FromJSON Vectors where+  parseJSON = Json.withObject "vectors" $ \o -> Vectors <$> o .: "invalid" <*> o .: "valid" <*> o .: "roles" <*> o .: "extracted"++-- The PSBTs of the walk-through, by their place in it+creator, updater, updaterWithSigHash, firstSigner, secondSigner, combiner, finalizer, unknownA, unknownB, unknownCombined, toCombineA, toCombineB, combined :: Vectors -> Vector+creator v = roles v !! 0+updater v = roles v !! 1+updaterWithSigHash v = roles v !! 2+firstSigner v = roles v !! 3+secondSigner v = roles v !! 4+combiner v = roles v !! 5+finalizer v = roles v !! 6+unknownA v = roles v !! 7+unknownB v = roles v !! 8+unknownCombined v = roles v !! 9+toCombineA v = roles v !! 10+toCombineB v = roles v !! 11+combined v = roles v !! 12++hexBytes :: Text -> ByteString+hexBytes = fromJust . decodeHex++bytesOf :: Vector -> ByteString+bytesOf = hexBytes . vecHex++expectRight :: Either String a -> IO a+expectRight = either fail return++forceRight :: Either String a -> a+forceRight = either error id++fingerprintOf :: Text -> Fingerprint+fingerprintOf = forceRight . textToFingerprint++-- | The PSBT of a vector, which must decode+psbtOfVector :: Vector -> IO Psbt+psbtOfVector v = expectRight (decodePsbt (bytesOf v))++-- | The map of the input, or of the output, at the index+inputAt, outputAt :: Int -> Psbt -> PsbtMap+inputAt i psbt = psbt.inputs !! i+outputAt i psbt = psbt.outputs !! i++-- | A PSBT with the maps of its inputs replaced+withInputs :: [PsbtMap] -> Psbt -> Psbt+withInputs inputs psbt = Psbt psbt.version psbt.unsignedTx psbt.globals inputs psbt.outputs++-- | The PSBT with the flags given in its global map+withFlags :: Modifiable -> Psbt -> Psbt+withFlags flags psbt = Psbt psbt.version psbt.unsignedTx (insertTxModifiable flags psbt.globals) psbt.inputs psbt.outputs++spec :: Spec+spec = do+  vectors <- runIO (readTestFile "psbt_bip174.json" :: IO Vectors)+  vectors370 <- runIO (readTestFile "psbt_bip370.json" :: IO Vectors370)+  vectors371 <- runIO (readTestFile "psbt_bip371.json" :: IO Vectors371)+  prepareContext $ \ctx -> do+    vectorsSpec vectors+    vectors370Spec vectors370+    rolesSpec ctx vectors+    version2Spec+    bytesSpec vectors+    fieldsSpec ctx vectors+    signingSpec ctx+    signerChecksSpec ctx+    taprootSpec ctx vectors371+    complexSpec ctx+    core32Spec ctx+    propertiesSpec ctx++{- The vectors of BIP 370 -}++-- The vectors of BIP 370 live in data/psbt_bip370.json: the invalid+-- PSBTs, the valid ones, and the PSBTs of its lock time cases, each with+-- the lock time the BIP computes, none when it cannot be computed+data LockTimeVector = LockTimeVector+  { ltCase :: String,+    ltHex :: Text,+    ltLockTime :: Maybe Word32+  }++instance Json.FromJSON LockTimeVector where+  parseJSON = Json.withObject "vector" $ \o -> LockTimeVector <$> o .: "case" <*> o .: "hex" <*> o .:? "locktime"++data Vectors370 = Vectors370+  { refused :: [Vector],+    accepted :: [Vector],+    lockTimes :: [LockTimeVector]+  }++instance Json.FromJSON Vectors370 where+  parseJSON = Json.withObject "vectors" $ \o -> Vectors370 <$> o .: "invalid" <*> o .: "valid" <*> o .: "locktime"++-- | Why each invalid PSBT of BIP 370 is refused, in the order of the BIP:+-- a field of the other version, a field version 2 requires missing, a+-- lock time out of its range+refusals370 :: [String]+refusals370 =+  [ "The global map: the key 0x00 is not of PSBT version 2",+    "The global map: the key 0x02 is not of PSBT version 0",+    "The global map: the key 0x03 is not of PSBT version 0",+    "The global map: the key 0x04 is not of PSBT version 0",+    "The global map: the key 0x05 is not of PSBT version 0",+    "The global map: the key 0x06 is not of PSBT version 0",+    "Input 0: the key 0x0e is not of PSBT version 0",+    "Input 0: the key 0x0f is not of PSBT version 0",+    "Input 0: the key 0x10 is not of PSBT version 0",+    "Input 0: the key 0x11 is not of PSBT version 0",+    "Input 0: the key 0x12 is not of PSBT version 0",+    "Output 0: the key 0x03 is not of PSBT version 0",+    "Output 0: the key 0x04 is not of PSBT version 0",+    "The global map: the key 0x00 is not of PSBT version 2",+    "The global map has no input count",+    "The global map has no output count",+    "The global map has no transaction version",+    "Input 0 has no previous transaction id",+    "Input 0 has no output index",+    "Output 0 has no amount",+    "Output 0 has no script",+    "Input 0: the value of the key 0x11 is a time lock below 500000000",+    "Input 0: the value of the key 0x12 is not a height between 1 and 499999999",+    "Input 0: the value of the key 0x12 is not a height between 1 and 499999999"+  ]++vectors370Spec :: Vectors370 -> Spec+vectors370Spec vectors = do+  describe "The invalid PSBTs of BIP 370 are refused, each for its reason" $+    forM_ (zip vectors.refused refusals370) $ \(v, reason) ->+      it v.vecCase $ decodePsbt (bytesOf v) `shouldBe` Left reason++  describe "The valid PSBTs of BIP 370 are read as version 2, and written back byte for byte" $+    forM_ vectors.accepted $ \v ->+      it v.vecCase $ do+        psbt <- psbtOfVector v+        psbt.version `shouldBe` Version2+        encodePsbt psbt `shouldBe` bytesOf v++  describe "The transaction of a PSBT of version 2 is the one its fields describe" $+    -- The first valid PSBT of the BIP: one input, two outputs, nothing+    -- else. Its input has no sequence field, so its sequence is final,+    -- and no input requires a lock time, nor is a fallback given: lock+    -- time 0.+    it "for the first valid PSBT of the BIP, with a sequence and a lock time that no field gives" $ do+      psbt <- psbtOfVector (vectors.accepted !! 0)+      psbt.unsignedTx+        `shouldBe` Tx+          2+          [TxIn (OutPoint "c85f81844094f9f0eec1e41f8d63e0a99e9f73dc725d7319871c9c4121d90a0b" 0) "" 0xffffffff]+          [ TxOut 800000000 (hexBytes "0014c430f64c4756da310dbd1a085572ef299926272c"),+            TxOut 199998859 (hexBytes "00144dd193ac964a56ac1b9e1cca8454fe2f474f8513")+          ]+          []+          0++  describe "The lock time of a PSBT of version 2 is the one BIP 370 determines" $+    forM_ vectors.lockTimes $ \v ->+      it v.ltCase $+        ((.unsignedTx.locktime) <$> decodePsbt (hexBytes v.ltHex))+          `shouldBe` maybe (Left "No lock time fits every input: some require a height, others a time") Right v.ltLockTime++{- The vectors -}++vectorsSpec :: Vectors -> Spec+vectorsSpec vectors = do+  describe "The invalid PSBTs of BIP 174 are refused" $+    forM_ vectors.invalid $ \v ->+      it v.vecCase $ decodePsbt (bytesOf v) `shouldSatisfy` isLeft++  describe "The valid PSBTs of BIP 174 are read, and written back byte for byte" $+    forM_ vectors.valid $ \v ->+      it v.vecCase $ (encodePsbt <$> decodePsbt (bytesOf v)) `shouldBe` Right (bytesOf v)++  describe "The valid PSBTs of BIP 174 read the same from their base64 text" $+    forM_ [(v, text) | v <- vectors.valid, Just text <- [v.vecBase64]] $ \(v, text) ->+      it v.vecCase $ (encodePsbt <$> decodePsbtBase64 (encodeUtf8 text)) `shouldBe` Right (bytesOf v)++  -- The combiner's PSBT of the two signed ones lists the signatures of+  -- its second input out of order: see 'rolesSpec'+  describe "The PSBTs of the roles of BIP 174 are read, and written back byte for byte" $+    forM_ [v | v <- vectors.roles, v.vecCase /= (combiner vectors).vecCase] $ \v ->+      it v.vecCase $ (encodePsbt <$> decodePsbt (bytesOf v)) `shouldBe` Right (bytesOf v)++rolesSpec :: Ctx -> Vectors -> Spec+rolesSpec ctx vectors =+  describe "The roles of BIP 174" $ do+    it "the creator's PSBT is the unsigned transaction with empty maps" $ do+      psbt <- psbtOfVector (creator vectors)+      psbtOf psbt.unsignedTx `shouldBe` psbt++    it "the transaction extractor gives the transaction of the finalizer's PSBT" $ do+      psbt <- psbtOfVector (finalizer vectors)+      (S.encode <$> psbtTransaction psbt) `shouldBe` Right (bytesOf (extracted vectors))++    it "the transaction of a PSBT without final scripts is the unsigned one" $ do+      psbt <- psbtOfVector (creator vectors)+      psbtTransaction psbt `shouldBe` Right psbt.unsignedTx++    -- The spec does not say in which order a combiner writes the keys of a+    -- map. The BIP's combiner lists the two signatures of the second input+    -- out of order; the keys of a map are written in order here, as+    -- Bitcoin Core writes them. The PSBTs are the same, byte for byte once+    -- the BIP's is written back.+    it "the combiner's PSBT is that of the two signers combined, whatever the order of its inputs" $ do+      a <- psbtOfVector (firstSigner vectors)+      b <- psbtOfVector (secondSigner vectors)+      both <- psbtOfVector (combiner vectors)+      combinePsbt a b `shouldBe` Right both+      combinePsbt b a `shouldBe` Right both+      encodePsbt both `shouldNotBe` bytesOf (combiner vectors)+      decodePsbt (encodePsbt both) `shouldBe` Right both++    it "the two PSBTs with unknown keys combine as the combiner of the BIP that orders keys lexicographically does" $ do+      a <- psbtOfVector (unknownA vectors)+      b <- psbtOfVector (unknownB vectors)+      (encodePsbt <$> combinePsbt a b) `shouldBe` Right (bytesOf (unknownCombined vectors))+      (encodePsbt <$> combinePsbt b a) `shouldBe` Right (bytesOf (unknownCombined vectors))++    it "the two PSBTs to combine give the combiner's PSBT" $ do+      a <- psbtOfVector (toCombineA vectors)+      b <- psbtOfVector (toCombineB vectors)+      (encodePsbt <$> combinePsbt a b) `shouldBe` Right (bytesOf (combined vectors))++    it "PSBTs of different transactions are not combined" $ do+      a <- psbtOfVector (creator vectors)+      combinePsbt a (psbtOf emptyTx) `shouldBe` Left "The PSBTs are not of the same transaction"++    it "the updater's PSBT with SIGHASH_ALL is the updater's PSBT with the sighash type on every input" $ do+      psbt <- psbtOfVector (updater vectors)+      encodePsbt (withInputs (insertSigHashType sigHashAll <$> psbt.inputs) psbt) `shouldBe` bytesOf (updaterWithSigHash vectors)++    it "the finalizer's PSBT is the combiner's PSBT finalized" $ do+      psbt <- psbtOfVector (combiner vectors)+      (encodePsbt <$> finalizePsbt btc ctx psbt) `shouldBe` Right (bytesOf (finalizer vectors))++    it "the finalizer leaves an input without enough signatures as it is" $ do+      psbt <- psbtOfVector (firstSigner vectors)+      finalizePsbt btc ctx psbt `shouldBe` Right psbt++{- The roles of version 2 -}++-- | The input and output of the PSBTs below: the coin and the first output+-- of the first valid PSBT of BIP 370, and another output of the same+-- transaction as the coin+coinInput, otherInput :: TxIn+coinInput = TxIn (OutPoint "c85f81844094f9f0eec1e41f8d63e0a99e9f73dc725d7319871c9c4121d90a0b" 0) "" 0xfffffffd+otherInput = TxIn (OutPoint "c85f81844094f9f0eec1e41f8d63e0a99e9f73dc725d7319871c9c4121d90a0b" 1) "" 0xfffffffd++paidOutput :: TxOut+paidOutput = TxOut 800000000 (hexBytes "0014c430f64c4756da310dbd1a085572ef299926272c")++version2Spec :: Spec+version2Spec =+  describe "A PSBT of version 2" $ do+    -- A creator of BIP 370 starts with no input and no output. The+    -- created one, with its flags set so that inputs and outputs can be+    -- added, and with the input and the output added.+    let created = withVersion Version2 (psbtOf (Tx 2 [] [] [] 0))+        modifiable = withFlags (Modifiable True True False 0) created+        built = forceRight (addOutput paidOutput Map.empty =<< addInput coinInput Map.empty modifiable)+        -- The same without its flags, as a constructor leaves it once+        -- nothing is to be added+        unflagged = Psbt Version2 built.unsignedTx (deleteTxModifiable built.globals) built.inputs built.outputs+        requiring field value = addInput otherInput (field value Map.empty)++    -- The fields of the global map: the transaction version 2, the+    -- fallback lock time 0, the two counts of 0, and the PSBT version 2+    it "is created with no input and no output, and the fields that describe its transaction" $+      encodeHex (encodePsbt created) `shouldBe` "70736274ff0102040200000001030400000000010401000105010001fb040200000000"++    it "takes no input or output unless its flags allow it" $ do+      addInput coinInput Map.empty created `shouldBe` Left "The inputs of the PSBT may not be modified"+      addOutput paidOutput Map.empty created `shouldBe` Left "The outputs of the PSBT may not be modified"++    -- The input is described by the id of the transaction it spends from,+    -- in the byte order of a transaction, the index of the output and its+    -- sequence; the output by its amount and its script+    it "takes an input and an output at the end, counted, described in their maps, and in its transaction" $ do+      built.unsignedTx `shouldBe` Tx 2 [coinInput] [paidOutput] [] 0+      Map.lookup (Key 0x04 "") built.globals `shouldBe` Just "\x01"+      Map.lookup (Key 0x05 "") built.globals `shouldBe` Just "\x01"+      inputAt 0 built+        `shouldBe` Map.fromList+          [ (Key 0x0e "", hexBytes "0b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8"),+            (Key 0x0f "", hexBytes "00000000"),+            (Key 0x10 "", hexBytes "fdffffff")+          ]+      outputAt 0 built `shouldBe` Map.fromList [(Key 0x03 "", hexBytes "0008af2f00000000"), (Key 0x04 "", hexBytes "0014c430f64c4756da310dbd1a085572ef299926272c")]++    it "refuses an input that spends an output it spends already" $+      addInput coinInput Map.empty built `shouldBe` Left "An input of the PSBT spends that output already"++    it "is not of version 0, which takes no input or output" $+      addInput coinInput Map.empty (psbtOf emptyTx) `shouldBe` Left "A PSBT of version 0 takes no input or output: its transaction is fixed"++    it "takes the lock time its inputs require: the greatest height when every one that requires a lock time takes a height" $ do+      withHeight <- expectRight (addInput coinInput (insertRequiredHeightLockTime 10000 Map.empty) modifiable)+      withHeight.unsignedTx.locktime `shouldBe` 10000+      ((.unsignedTx.locktime) <$> requiring insertRequiredHeightLockTime 9000 withHeight) `shouldBe` Right 10000+      requiring insertRequiredTimeLockTime 1657048460 withHeight `shouldBe` Left "No lock time fits every input: some require a height, others a time"++    -- The input of the PSBT has its final script+    it "refuses an input that changes the lock time once an input is signed, and takes one that keeps it" $ do+      let signed = withInputs [insertFinalScriptSig "\x00" (inputAt 0 built)] built+      requiring insertRequiredHeightLockTime 10000 signed `shouldBe` Left "An input is signed already: the lock time may not change"+      ((.unsignedTx.locktime) <$> addInput otherInput Map.empty signed) `shouldBe` Right 0++    it "takes a sequence for an input, in its transaction and in its map, as an updater sets one" $ do+      updated <- expectRight (setInputSequence 0 1 built)+      map (.sequence) updated.unsignedTx.inputs `shouldBe` [1]+      Map.lookup (Key 0x10 "") (inputAt 0 updated) `shouldBe` Just (hexBytes "01000000")+      setInputSequence 1 1 built `shouldBe` Left "The PSBT has no input 1"+      setInputSequence 0 1 (psbtOf emptyTx) `shouldBe` Left "The sequences of a PSBT of version 0 are in its unsigned transaction, which is fixed"+      -- A signature commits to the sequence of its input+      setInputSequence 0 1 (withInputs [insertFinalScriptSig "\x00" (inputAt 0 built)] built) `shouldBe` Left "Input 0 is signed already: its sequence may not change"++    -- The id of its transaction with the sequence of its input at 0: the+    -- same whatever the sequence. A PSBT of version 0 is identified by+    -- its transaction as it is, with the sequence 0xfffffffd here.+    it "is identified by its transaction with every sequence at 0" $ do+      psbtId built `shouldBe` "79dd9a151a6cfc0dc3b7779a697817a80544e2c9dc0cfb399ecc7fa3b4da8f2c"+      (psbtId <$> setInputSequence 0 1 built) `shouldBe` Right "79dd9a151a6cfc0dc3b7779a697817a80544e2c9dc0cfb399ecc7fa3b4da8f2c"+      psbtId (withVersion Version0 built) `shouldBe` "7ca9f2c2ff60a7a3f7efdaa703bfb23b9415639e07b71e194bafb4d592e2c405"++    it "combines with the same PSBT whose sequence an updater changed, and keeps the sequence of the first" $ do+      updated <- expectRight (setInputSequence 0 1 built)+      (map (.sequence) . (.unsignedTx.inputs) <$> combinePsbt built updated) `shouldBe` Right [0xfffffffd]+      (map (.sequence) . (.unsignedTx.inputs) <$> combinePsbt updated built) `shouldBe` Right [1]++    it "does not combine with a PSBT of version 0" $+      combinePsbt built (withVersion Version0 built) `shouldBe` Left "The PSBTs are not of the same version"++    -- The first lets inputs be added and has a SIGHASH_SINGLE signature;+    -- the second lets inputs and outputs be added, with the undefined+    -- bit 3 set+    it "combines its flags as Bitcoin Core does: modifiable only when both say so, with a SIGHASH_SINGLE signature of either" $ do+      (txModifiable . (.globals) <$> combinePsbt (withFlags (Modifiable True False True 0) built) (withFlags (Modifiable True True False 8) built))+        `shouldBe` Right (Right (Just (Modifiable True False True 0)))+      (txModifiable . (.globals) <$> combinePsbt unflagged (withFlags (Modifiable True False True 0) built))+        `shouldBe` Right (Right (Just (Modifiable False False True 0)))++    it "once signed, takes no more inputs unless the sighash has ANYONECANPAY, no more outputs unless it is NONE, and tells a SIGHASH_SINGLE signature" $ do+      let open = withFlags (Modifiable True True False 0) built+          flagsAfter sighash psbt = txModifiable (afterSignature sighash psbt).globals+      flagsAfter sigHashAll open `shouldBe` Right (Just (Modifiable False False False 0))+      flagsAfter (setAnyoneCanPay sigHashNone) open `shouldBe` Right (Just (Modifiable True True False 0))+      flagsAfter sigHashSingle open `shouldBe` Right (Just (Modifiable False False True 0))+      flagsAfter sigHashAll unflagged `shouldBe` Right Nothing+      flagsAfter sigHashSingle unflagged `shouldBe` Right (Just (Modifiable False False True 0))++    it "converted to version 0 is the PSBT of its transaction" $+      withVersion Version0 built `shouldBe` psbtOf (Tx 2 [coinInput] [paidOutput] [] 0)++{- Bytes -}++-- | A transaction with nothing in it: version 0, no input, no output, lock+-- time 0, which is "0000000000000000000000" over ten bytes+emptyTx :: Tx+emptyTx = Tx 0 [] [] [] 0++-- | The global map of the PSBT of 'emptyTx': the key of the unsigned+-- transaction, one byte of type 0x00, and the ten bytes of the transaction+emptyTxEntry :: Text+emptyTxEntry = "01000a00000000000000000000"++bytesSpec :: Vectors -> Spec+bytesSpec vectors =+  describe "The bytes of a PSBT" $ do+    -- The vector of the BIP for a transaction without inputs or outputs+    it "are the magic bytes, the unsigned transaction, and a map for each input and output" $+      encodePsbt (psbtOf emptyTx) `shouldBe` hexBytes ("70736274ff" <> emptyTxEntry <> "00")++    -- The key is 7 bytes: the type 0xfc, the identifier "test" after its+    -- length 4, and the subtype 0. The value is the 3 bytes of "btc".+    it "write a key as its length, its type and its data, and its value after its length" $+      encodePsbt (Psbt Version0 emptyTx (insertProprietary "test" 0 "btc" Map.empty) [] [])+        `shouldBe` hexBytes ("70736274ff" <> emptyTxEntry <> "07fc047465737400" <> "03627463" <> "00")++    it "write the keys of a map in order, the type first, whatever the order they were added" $ do+      let globals = Map.fromList [(Key 0x20 "", hexBytes "bb"), (Key 0x10 (hexBytes "01"), hexBytes "aa")]+      encodePsbt (Psbt Version0 emptyTx globals [] [])+        `shouldBe` hexBytes ("70736274ff" <> emptyTxEntry <> "02100101aa" <> "012001bb" <> "00")++    it "write the unsigned transaction without the input scripts and the witness of a signed one" $ do+      signed <- expectRight $ S.decode (bytesOf (extracted vectors))+      psbt <- psbtOfVector (finalizer vectors)+      (psbtOf signed).unsignedTx `shouldBe` psbt.unsignedTx++    it "tell a PSBT from other bytes by its first five" $ do+      isPsbt (bytesOf (creator vectors)) `shouldBe` True+      isPsbt "{\"tx\": \"0200\"}" `shouldBe` False++    it "accept the unsigned transaction after another key of the global map" $+      decodePsbt (hexBytes ("70736274ff" <> "012001bb" <> emptyTxEntry <> "00"))+        `shouldBe` Right (Psbt Version0 emptyTx (Map.fromList [(Key 0x20 "", hexBytes "bb")]) [] [])++    it "refuse what is not a PSBT" $+      decodePsbt "{\"tx\": \"0200\"}" `shouldBe` Left "Not a PSBT: the bytes do not start with the magic bytes of BIP 174"++    it "refuse a key twice in a map, naming it" $+      decodePsbt (hexBytes ("70736274ff" <> emptyTxEntry <> "012001aa" <> "012001bb" <> "00"))+        `shouldBe` Left "The global map has the key 0x20 twice"++    it "refuse bytes after the last map" $+      decodePsbt (bytesOf (creator vectors) <> "\0") `shouldBe` Left "Bytes follow the last map"++    -- Version 1 was never defined+    it "refuse a PSBT of a version other than 0 and 2" $+      decodePsbt (hexBytes ("70736274ff" <> emptyTxEntry <> "01fb0401000000" <> "00"))+        `shouldBe` Left "The PSBT is of version 1: only versions 0 and 2 are supported"++    -- The length of the first key, 1, written over three bytes+    it "refuse a length that is not in its shortest form" $+      decodePsbt (hexBytes ("70736274ff" <> "fd0100" <> "000a00000000000000000000" <> "00"))+        `shouldBe` Left "A length in the global map is not in its shortest form"++    -- The PSBT with 0 inputs and 2 outputs of the BIP, without the two+    -- bytes that end its output maps+    it "refuse bytes that end inside a map" $ do+      let withoutOutputs = bytesOf (vectors.valid !! 9)+      decodePsbt (B.take (B.length withoutOutputs - 2) withoutOutputs) `shouldBe` Left "The PSBT ends inside output 0"++    it "refuse a key of the spec that carries data it should not, naming it" $+      decodePsbt (bytesOf (vectors.invalid !! 15)) `shouldBe` Left "Input 0: the key 0x03 with the data 00 carries data"++    it "refuse a derivation key that is not a public key" $+      decodePsbt (bytesOf (vectors.invalid !! 10)) `shouldSatisfy` either ("is not a public key" `isSuffixOf`) (const False)++    it "read the base64 text of a PSBT, with spaces and line breaks around it" $ do+      let v = vectors.valid !! 0+      (encodePsbt <$> decodePsbtBase64 (" " <> encodeUtf8 (fromMaybe "" v.vecBase64) <> "\n")) `shouldBe` Right (bytesOf v)++    it "write the base64 text the BIP gives" $ do+      let v = vectors.valid !! 0+      psbt <- psbtOfVector v+      Just (encodePsbtBase64 psbt) `shouldBe` (encodeUtf8 <$> v.vecBase64)++    it "refuse text that is not base64" $+      decodePsbtBase64 "not base64!" `shouldBe` Left "Not the base64 text of a PSBT"++{- Fields -}++-- | The signature of the first signer of the BIP on the first input, as+-- Bitcoin Core decodes its PSBT+firstSignature :: Text+firstSignature = "3044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01"++-- | The key of the first signer of the BIP, at m/0'/0'/0' of the master+-- key d90c6a4f+firstKey :: Text+firstKey = "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f"++-- The literal values below are those Bitcoin Core 25.0 shows for the+-- vectors with decodepsbt.+fieldsSpec :: Ctx -> Vectors -> Spec+fieldsSpec ctx vectors =+  describe "The fields of a PSBT" $ do+    let publicKey text = forceRight (unmarshal ctx (hexBytes text)) :: PublicKey+        shown :: [(PublicKey, Derivation)] -> [(Text, Derivation)]+        shown records = [(encodeHex (marshal ctx key), derivation) | (key, derivation) <- records]++    it "read the derivation records of an input: the public key, the fingerprint of the master key and the path" $ do+      psbt <- psbtOfVector (updater vectors)+      records <- expectRight $ inputDerivations ctx (inputAt 0 psbt)+      shown records+        `shouldBe` [ (firstKey, Derivation (fingerprintOf "d90c6a4f") "m/0'/0'/0'"),+                     ("02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7", Derivation (fingerprintOf "d90c6a4f") "m/0'/0'/1'")+                   ]++    it "read the derivation records of an output" $ do+      psbt <- psbtOfVector (updater vectors)+      records <- expectRight $ outputDerivations ctx (outputAt 0 psbt)+      shown records `shouldBe` [("03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58771", Derivation (fingerprintOf "d90c6a4f") "m/0'/0'/4'")]++    -- The path is 0' (0x80000000) three times, each over four bytes,+    -- little-endian+    it "write a derivation record as the public key under the type, and the fingerprint then the path, four bytes an index" $ do+      let key = publicKey firstKey+      Map.toList (insertInputDerivation ctx key (Derivation (fingerprintOf "d90c6a4f") "m/0'/0'/0'") Map.empty)+        `shouldBe` [(Key 0x06 (hexBytes firstKey), hexBytes ("d90c6a4f" <> "00000080" <> "00000080" <> "00000080"))]+      Map.keys (insertOutputDerivation ctx key (Derivation (fingerprintOf "d90c6a4f") "m/0'/0'/4'") Map.empty)+        `shouldBe` [Key 0x02 (hexBytes firstKey)]++    it "read the sighash type of an input, when it has one" $ do+      with <- psbtOfVector (updaterWithSigHash vectors)+      without <- psbtOfVector (updater vectors)+      mapM sigHashType with.inputs `shouldBe` Right [Just sigHashAll, Just sigHashAll]+      mapM sigHashType without.inputs `shouldBe` Right [Nothing, Nothing]++    -- SIGHASH_ALL with the FORKID flag of Bitcoin Cash, 0x41+    it "write the sighash type over four bytes, little-endian, and drop it as a finalizer does" $ do+      Map.toList (insertSigHashType (setForkIdFlag sigHashAll) Map.empty) `shouldBe` [(Key 0x03 "", hexBytes "41000000")]+      Map.keys (deleteSigHashType (insertSigHashType sigHashAll (insertNonWitnessUtxo emptyTx Map.empty))) `shouldBe` [Key 0x00 ""]++    it "read the whole previous transaction of an input" $ do+      psbt <- psbtOfVector (vectors.valid !! 0)+      parent <- expectRight $ nonWitnessUtxo (inputAt 0 psbt)+      (txHashToHex . txHash <$> parent) `shouldBe` Just "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126"++    it "write a previous transaction under the type 0x00" $ do+      psbt <- psbtOfVector (vectors.valid !! 0)+      parent <- expectRight $ nonWitnessUtxo (inputAt 0 psbt)+      Map.toList (insertNonWitnessUtxo emptyTx Map.empty) `shouldBe` [(Key 0x00 "", hexBytes "00000000000000000000")]+      nonWitnessUtxo (maybe Map.empty (`insertNonWitnessUtxo` Map.empty) parent) `shouldBe` Right parent++    -- The second input of the vector spends 1 BTC at a P2SH address+    it "read the output an input spends when the PSBT has it alone, and write it under the type 0x01" $ do+      psbt <- psbtOfVector (vectors.valid !! 1)+      let coin = TxOut 100000000 (hexBytes "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787")+      mapM witnessUtxo psbt.inputs `shouldBe` Right [Nothing, Just coin]+      Map.toList (insertWitnessUtxo coin Map.empty) `shouldBe` [(Key 0x01 "", hexBytes "00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787")]++    it "tell an input with partial signatures from one without" $ do+      partial <- psbtOfVector (vectors.valid !! 4)+      unsigned <- psbtOfVector (vectors.valid !! 0)+      hasPartialSignatures (inputAt 0 partial) `shouldBe` True+      hasPartialSignatures (inputAt 0 unsigned) `shouldBe` False++    -- The first signer of the BIP signs the first input with SIGHASH_ALL,+    -- the last byte 01 of the signature+    it "read the partial signatures of an input: the public key, and the signature with its sighash byte" $ do+      psbt <- psbtOfVector (firstSigner vectors)+      records <- expectRight (partialSignatures ctx (inputAt 0 psbt))+      [(encodeHex (marshal ctx key), encodeHex signature) | (key, signature) <- records] `shouldBe` [(firstKey, firstSignature)]++    it "write a partial signature under the type 0x02 with the public key, and drop them all as a finalizer does" $ do+      let m = insertPartialSignature ctx (publicKey firstKey) (hexBytes firstSignature) (insertSigHashType sigHashAll Map.empty)+      Map.toList m `shouldBe` [(Key 0x02 (hexBytes firstKey), hexBytes firstSignature), (Key 0x03 "", hexBytes "01000000")]+      Map.toList (deletePartialSignatures m) `shouldBe` [(Key 0x03 "", hexBytes "01000000")]++    -- The second input of the updater's PSBT spends a P2SH-P2WSH coin: a+    -- 2-of-2 multisig behind a witness program+    it "read the redeem script and the witness script of an input, and write them under the types 0x04 and 0x05" $ do+      psbt <- psbtOfVector (updater vectors)+      let redeem = Script [OP_0, opPushData (hexBytes "8c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903")]+          witness = Script [OP_2, opPushData (hexBytes "03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc"), opPushData (hexBytes "023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73"), OP_2, OP_CHECKMULTISIG]+      inputRedeemScript (inputAt 1 psbt) `shouldBe` Right (Just redeem)+      inputWitnessScript (inputAt 1 psbt) `shouldBe` Right (Just witness)+      inputRedeemScript (inputAt 0 psbt) `shouldSatisfy` (\found -> fmap (const ()) found == Right () && found /= Right Nothing)+      Map.toList (insertInputRedeemScript redeem (insertInputWitnessScript witness Map.empty))+        `shouldBe` [ (Key 0x04 "", hexBytes "00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"),+                     (Key 0x05 "", hexBytes "522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae")+                   ]++    it "read the redeem script and the witness script of an output, and write them under the types 0x00 and 0x01" $ do+      let redeem = Script [OP_0, opPushData (hexBytes "8c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903")]+          witness = Script [OP_1, OP_CHECKMULTISIG]+          m = insertOutputRedeemScript redeem (insertOutputWitnessScript witness Map.empty)+      Map.keys m `shouldBe` [Key 0x00 "", Key 0x01 ""]+      outputRedeemScript m `shouldBe` Right (Just redeem)+      outputWitnessScript m `shouldBe` Right (Just witness)+      outputWitnessScript Map.empty `shouldBe` Right Nothing++    it "read the final script and witness of a signed input" $ do+      psbt <- psbtOfVector (finalizer vectors)+      let first' = inputAt 0 psbt+          second = inputAt 1 psbt+      finalScriptSig first'+        `shouldBe` Just (hexBytes "00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae")+      finalScriptWitness first' `shouldBe` Right Nothing+      finalScriptSig second `shouldBe` Just (hexBytes "2200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903")+      finalScriptWitness second+        `shouldBe` Right+          ( Just+              [ "",+                hexBytes "3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01",+                hexBytes "3044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d201",+                hexBytes "522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"+              ]+          )++    it "write a final script under the type 0x07, and a final witness as the number of its items then each after its length" $ do+      Map.toList (insertFinalScriptSig (hexBytes "aabb") Map.empty) `shouldBe` [(Key 0x07 "", hexBytes "aabb")]+      Map.toList (insertFinalScriptWitness [hexBytes "aabb", ""] Map.empty) `shouldBe` [(Key 0x08 "", hexBytes "0202aabb00")]++    it "read the extended public keys of the global map, with their derivations, and write them under the type 0x01" $ do+      psbt <- psbtOfVector (vectors.valid !! 7)+      let xpub = fromJust $ xPubImport btc ctx "xpub6CpGH79LXVkeiux2ZPWMpEubBrRfgcGCgy2HiagyN6NW3qdioJaqFYyD1fG6LDfxWEhMXJqcDuU5VneKt5UQYUGPa5Mfxdw2D2NArwX5TBm"+          record = (xpub, Derivation (fingerprintOf "27569c50") "m/49'/0'/0'")+      xpubRecords btc ctx psbt.globals `shouldBe` Right [record]+      let m = insertXpubRecord btc ctx xpub (snd record) Map.empty+      Map.keys m `shouldBe` [Key 0x01 (marshal (btc, ctx) xpub)]+      xpubRecords btc ctx m `shouldBe` Right [record]++    -- The two keys of the vector are of the test network+    it "leave aside the extended public keys of another network" $ do+      psbt <- psbtOfVector (vectors.valid !! 5)+      let tpub text = fromJust $ xPubImport btcTest ctx text+      xpubRecords btc ctx psbt.globals `shouldBe` Right []+      xpubRecords btcTest ctx psbt.globals+        `shouldBe` Right+          [ (tpub "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H", Derivation (fingerprintOf "d90c6a4f") "m/174'/0'"),+            (tpub "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6", Derivation (fingerprintOf "d90c6a4f") "m/174'/1'")+          ]++    it "write a proprietary key as the type 0xfc, the identifier after its length, the subtype and the data" $ do+      proprietaryKey (Proprietary "test" 1 (hexBytes "ab")) `shouldBe` Key 0xfc (hexBytes "047465737401ab")+      proprietaryOf (Key 0xfc (hexBytes "047465737401ab")) `shouldBe` Right (Just (Proprietary "test" 1 (hexBytes "ab")))+      proprietaryOf (Key 0x20 "") `shouldBe` Right Nothing+      proprietaryOf (Key 0xfc "") `shouldBe` Left "the proprietary key 0xfc has no identifier and subtype"++    it "look a proprietary field up by its identifier and subtype" $ do+      let m = insertProprietary "test" 3 "Rent" Map.empty+      lookupProprietary "test" 3 m `shouldBe` Just "Rent"+      lookupProprietary "test" 2 m `shouldBe` Nothing+      lookupProprietary "other" 3 m `shouldBe` Nothing++{- Signing and finalizing -}++-- | A coin of 2 BTC at the script, spent whole by a transaction paying+-- two outputs, as a PSBT with the coin on its input: the whole previous+-- transaction, and the output alone for a SegWit coin+spendingPsbt :: Ctx -> ScriptOutput -> (Psbt, [(ScriptOutput, Word64, OutPoint)])+spendingPsbt ctx coinScript = (Psbt Version0 tx Map.empty [input] [Map.empty, Map.empty], [(coinScript, 200000000, outpoint)])+  where+    parent = Tx 1 [TxIn (OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1) "" 0xffffffff] [TxOut 200000000 (marshal ctx coinScript)] [] 0+    outpoint = OutPoint (txHash parent) 0+    tx =+      Tx+        2+        [TxIn outpoint "" 0xfffffffe]+        [ TxOut 99999699 (hexBytes "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"),+          TxOut 100000000 (hexBytes "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787")+        ]+        []+        1257139+    input+      | isSegwit coinScript = insertWitnessUtxo (TxOut 200000000 (marshal ctx coinScript)) Map.empty+      | otherwise = insertNonWitnessUtxo parent Map.empty++-- | A derivation record of a key, which a signer needs: the fingerprint+-- and the path are those of the BIP's first signer+recordOf :: Ctx -> PublicKey -> PsbtMap -> PsbtMap+recordOf ctx public = insertInputDerivation ctx public (Derivation (fingerprintOf "d90c6a4f") "m/0'/0'/0'")++-- | A signer holding the keys+keysSigner :: Ctx -> [PrivateKey] -> Signer+keysSigner ctx keys =+  Signer+    { keyOfRecord = \public derivation -> listToMaybe (mapMaybe (\key -> (keySigner ctx key.key).keyOfRecord public derivation) keys),+      keyOfHash = \h -> listToMaybe (mapMaybe (\key -> (keySigner ctx key.key).keyOfHash h) keys),+      acceptedSigHashes = defaultSigHashes+    }++-- | Whether the transaction of the PSBT, once signed with the keys and+-- finalized, verifies against the coins it spends+signsAndVerifies :: Network -> Ctx -> [PrivateKey] -> (Psbt, [(ScriptOutput, Word64, OutPoint)]) -> Either String Bool+signsAndVerifies net ctx keys (psbt, coins) = do+  signed <- signPsbt net ctx (keysSigner ctx keys) psbt+  final <- finalizePsbt btc ctx signed+  tx <- psbtTransaction final+  return (verifyStdTx net ctx tx coins)++signingSpec :: Ctx -> Spec+signingSpec ctx =+  describe "Signing and finalizing" $ do+    prop "a P2PKH coin, signed with the key of its address and finalized, verifies" $+      forAll (arbitraryKeyPair ctx) $ \(secret, public) ->+        let (psbt, coins) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+            withRecord = withInputs (recordOf ctx public <$> psbt.inputs) psbt+         in signsAndVerifies btc ctx [secret] (withRecord, coins) === Right True++    -- The finalized input keeps its coin, its final script, and the+    -- fields of version 2: the transaction it spends from, the output+    -- index and the sequence+    prop "a P2PKH coin of a PSBT of version 2, signed and finalized, verifies, and the PSBT stays of version 2 with the fields of its input" $+      forAll (arbitraryKeyPair ctx) $ \(secret, public) ->+        let (psbt, coins) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+            version2 = withVersion Version2 (withInputs (recordOf ctx public <$> psbt.inputs) psbt)+            final = finalizePsbt btc ctx =<< signPsbt btc ctx (keysSigner ctx [secret]) version2+         in (signsAndVerifies btc ctx [secret] (version2, coins), (.version) <$> final, Map.keys . inputAt 0 <$> final)+              === (Right True, Right Version2, Right [Key 0x00 "", Key 0x07 "", Key 0x0e "", Key 0x0f "", Key 0x10 ""])++    prop "a P2SH multisig coin, signed with enough of its keys and finalized, verifies" $+      forAll ((,) <$> arbitraryMSParam <*> arbitraryKeyPair ctx) $ \((m, n), _) ->+        forAll (vectorOf n (arbitraryKeyPair ctx)) $ \pairs ->+          let redeem = either error id $ encodeOutput ctx (PayMulSig (snd <$> pairs) m)+              (psbt, coins) = spendingPsbt ctx (toP2SH redeem)+              withFields = withInputs ((insertInputRedeemScript redeem . foldr (\(_, public) f -> recordOf ctx public . f) id pairs) <$> psbt.inputs) psbt+           in signsAndVerifies btc ctx (fst <$> take m pairs) (withFields, coins) === Right True++    prop "a P2WPKH coin, and a P2SH-P2WPKH one with its redeem script, signed and finalized, verify" $+      forAll (arbitraryKeyPair ctx) $ \(secret, public) ->+        let program = PayWitnessPKHash (addressHash (marshal ctx public))+            (native, nativeCoins) = spendingPsbt ctx program+            redeem = either error id $ encodeOutput ctx program+            (nested, nestedCoins) = spendingPsbt ctx (toP2SH redeem)+            nativeWithRecord = withInputs (recordOf ctx public <$> native.inputs) native+            nestedWithFields = withInputs ((insertInputRedeemScript redeem . recordOf ctx public) <$> nested.inputs) nested+         in signsAndVerifies btc ctx [secret] (nativeWithRecord, nativeCoins) === Right True+              .&&. signsAndVerifies btc ctx [secret] (nestedWithFields, nestedCoins) === Right True++    prop "a P2WSH multisig coin with its witness script, nested in P2SH or not, signed and finalized, verifies" $+      forAll arbitraryMSParam $ \(m, n) ->+        forAll (vectorOf n (arbitraryKeyPair ctx)) $ \pairs ->+          let witness = either error id $ encodeOutput ctx (PayMulSig (snd <$> pairs) m)+              program = toP2WSH witness+              records = foldr (\(_, public) f -> recordOf ctx public . f) id pairs+              (native, nativeCoins) = spendingPsbt ctx program+              (nested, nestedCoins) = spendingPsbt ctx (toP2SH programScript)+              nativeWithFields = withInputs ((insertInputWitnessScript witness . records) <$> native.inputs) native+              nestedWithFields = withInputs ((insertInputRedeemScript programScript . insertInputWitnessScript witness . records) <$> nested.inputs) nested+              programScript = either error id $ encodeOutput ctx program+           in signsAndVerifies btc ctx (fst <$> take m pairs) (nativeWithFields, nativeCoins) === Right True+                .&&. signsAndVerifies btc ctx (fst <$> take m pairs) (nestedWithFields, nestedCoins) === Right True++    it "signs with the sighash the input asks for when the signer accepts it, SIGHASH_ALL when it asks for none" $ do+      (secret, public) <- generate (arbitraryKeyPair ctx)+      let (psbt, _) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+          asking sh = withInputs ((recordOf ctx public . maybe id insertSigHashType sh) <$> psbt.inputs) psbt+          sighashOf signed = map (B.last . snd) <$> partialSignatures ctx (inputAt 0 signed)+          acceptingNone = (keySigner ctx secret.key) {acceptedSigHashes = sigHashNone : defaultSigHashes}+      (sighashOf =<< signPsbt btc ctx (keySigner ctx secret.key) (asking Nothing)) `shouldBe` Right [0x01]+      (sighashOf =<< signPsbt btc ctx (keySigner ctx secret.key) (asking (Just sigHashAll))) `shouldBe` Right [0x01]+      -- SIGHASH_NONE leaves the outputs unsigned, so the signer must agree to it+      signPsbt btc ctx (keySigner ctx secret.key) (asking (Just sigHashNone)) `shouldBe` Left "Input 0: the sighash type 2 is not one the signer accepts"+      (sighashOf =<< signPsbt btc ctx acceptingNone (asking (Just sigHashNone))) `shouldBe` Right [0x02]++    it "leaves an input without its coin, or signed already, as it is" $ do+      (secret, public) <- generate (arbitraryKeyPair ctx)+      let (psbt, _) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+          withoutCoin = withInputs [recordOf ctx public Map.empty] psbt+          final = withInputs ((insertFinalScriptSig "a script" . recordOf ctx public) <$> psbt.inputs) psbt+      signPsbt btc ctx (keySigner ctx secret.key) withoutCoin `shouldBe` Right withoutCoin+      signPsbt btc ctx (keySigner ctx secret.key) final `shouldBe` Right final++    it "refuses a previous transaction that is not the one the input spends" $ do+      (_, public) <- generate (arbitraryKeyPair ctx)+      let (psbt, _) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+          wrong = withInputs [insertNonWitnessUtxo emptyTx Map.empty] psbt+      signPsbt btc ctx (Signer (\_ _ -> Nothing) (const Nothing) defaultSigHashes) wrong `shouldBe` Left "Input 0: The previous transaction of the input is not the one it spends"++    -- The derivation records name keys by the master key, b4a6ba67 with+    -- the full path, or by the extended key given, with the path from it+    it "finds the key an extended private key derives, by its own fingerprint or by the origin given" $ do+      let root = makeXPrvKey "PSBT signer test"+          rootFingerprint = xPubFP ctx (deriveXPubKey ctx root)+          account = derivePath ctx (Deriv :| 444 :: HardPath) root+          accountFingerprint = xPubFP ctx (deriveXPubKey ctx account)+          secretAt path = (derivePath ctx (path :: DerivPath) root).key+          publicOf secret = PublicKey (derivePubKey ctx secret) True+          signer = xPrvSigner ctx account (Just (Derivation rootFingerprint "m/444'"))+          unknownOrigin = xPrvSigner ctx account Nothing+      signer.keyOfRecord (publicOf (secretAt "m/444'/0")) (Derivation rootFingerprint "m/444'/0") `shouldBe` Just (secretAt "m/444'/0")+      signer.keyOfRecord (publicOf (secretAt "m/444'/1")) (Derivation accountFingerprint "m/1") `shouldBe` Just (secretAt "m/444'/1")+      signer.keyOfRecord (publicOf (secretAt "m/444'/1")) (Derivation rootFingerprint "m/445'/1") `shouldBe` Nothing+      signer.keyOfRecord (publicOf (secretAt "m/444'/1")) (Derivation accountFingerprint "m/2") `shouldBe` Nothing+      unknownOrigin.keyOfRecord (publicOf (secretAt "m/444'/0")) (Derivation rootFingerprint "m/444'/0") `shouldBe` Nothing+      unknownOrigin.keyOfRecord (publicOf (secretAt "m/444'/0")) (Derivation accountFingerprint "m/0") `shouldBe` Just (secretAt "m/444'/0")+      -- An extended key cannot know which path a hash is at+      signer.keyOfHash (addressHash (marshal ctx (publicOf (secretAt "m/444'/0")))) `shouldBe` Nothing++{- A PSBT of every kind of coin -}++-- | The PSBTs of data/complex_psbt.json: a transaction of twelve coins+-- of every kind, signed by Bitcoin Core one coin at a time, their+-- combination, its finalization and the transaction it gives+data Complex = Complex+  { signedPsbts :: [Psbt],+    combinedPsbt :: Psbt,+    completePsbt :: Psbt,+    finalTx :: Tx+  }++instance Json.FromJSON Complex where+  parseJSON = Json.withObject "complex" $ \o ->+    Complex+      <$> mapM (psbtAt o) ["miner_psbt", "p2pkh_psbt", "p2sh_ms_1_psbt", "p2sh_ms_2_psbt", "p2sh_pk_psbt", "p2sh_wsh_pk_psbt", "p2sh_wsh_ms_1_psbt", "p2sh_wsh_ms_2_psbt", "p2wpkh_psbt", "p2wsh_pk_psbt", "p2wsh_ms_1_psbt", "p2wsh_ms_2_psbt"]+      <*> psbtAt o "combined_psbt"+      <*> psbtAt o "complete_psbt"+      <*> (either fail return . S.decode . hexBytes =<< o .: "final_tx")+    where+      psbtAt o name = either fail return . decodePsbtBase64 . encodeUtf8 =<< o .: name++complexSpec :: Ctx -> Spec+complexSpec ctx =+  describe "The PSBT of twelve coins of every kind, which Bitcoin Core signed one at a time" $ do+    complex <- runIO (readTestFile "complex_psbt.json" :: IO Complex)+    it "combines into the PSBT Core combined" $+      case complex.signedPsbts of+        first' : rest -> foldM combinePsbt first' rest `shouldBe` Right complex.combinedPsbt+        [] -> expectationFailure "No signed PSBT"++    it "finalizes into the PSBT Core finalized" $+      finalizePsbt btc ctx complex.combinedPsbt `shouldBe` Right complex.completePsbt++    it "gives the transaction Core extracted" $+      psbtTransaction complex.completePsbt `shouldBe` Right complex.finalTx++{- The files of Bitcoin Core 32.1 -}++-- The files live in data/psbt_core32.json: the PSBT of a payment as+-- walletcreatefundedpsbt gives it in version 0 and in version 2, the+-- version 2 one signed by walletprocesspsbt without finalizing, then+-- finalized, and the transaction finalizepsbt extracts+data Core32 = Core32+  { preparedV0 :: Psbt,+    preparedV2 :: Psbt,+    signedV2 :: Psbt,+    finalizedV2 :: Psbt,+    extractedTx :: Tx+  }++instance Json.FromJSON Core32 where+  parseJSON = Json.withObject "core32" $ \o ->+    Core32+      <$> psbtAt o "prepared_v0"+      <*> psbtAt o "prepared_v2"+      <*> psbtAt o "signed_v2"+      <*> psbtAt o "finalized_v2"+      <*> (either fail return . S.decode . hexBytes =<< o .: "final_tx")+    where+      psbtAt o name = either fail return . decodePsbtBase64 . encodeUtf8 =<< o .: name++core32Spec :: Ctx -> Spec+core32Spec ctx =+  describe "The files of Bitcoin Core 32.1, which writes version 2 unless asked for version 0" $ do+    core <- runIO (readTestFile "psbt_core32.json" :: IO Core32)+    it "are the same payment in both versions: the version 2 file is the version 0 file converted, and back" $ do+      core.preparedV2.version `shouldBe` Version2+      core.preparedV2.unsignedTx `shouldBe` core.preparedV0.unsignedTx+      withVersion Version2 core.preparedV0 `shouldBe` core.preparedV2+      withVersion Version0 core.preparedV2 `shouldBe` core.preparedV0++    it "finalize into the PSBT Core finalized" $+      finalizePsbt btc ctx core.signedV2 `shouldBe` Right core.finalizedV2++    it "give the transaction Core extracted" $+      psbtTransaction core.finalizedV2 `shouldBe` Right core.extractedTx++    -- The records name the key of the coin by the fingerprint bc560f95 of+    -- a seed and the path m/44'/0'/0'/0/0: the account key at m/44'/0'/0'+    -- of that seed signs. The coin is 50 BTC at the P2PKH script of that+    -- key.+    it "are signed with the account key the records name, into a transaction that verifies" $ do+      let account = fromJust (xPrvImport btc "xprv9yHxeaLAZvxXb9VtJNesqk8avfN8misGAW9DUW9eacZJNqsfZxqKLmK5jfmvFideQqGesviJeagzSQYCuQySjgvt7TdfowKja5aJqbgyuNh")+          signer = xPrvSigner ctx account (Just (Derivation (fingerprintOf "bc560f95") "m/44'/0'/0'"))+      coin <- expectRight (unmarshal ctx (hexBytes "76a914c80d55223e2f1061363add2a7b0363c285bf298d88ac"))+      final <- expectRight (finalizePsbt btc ctx =<< signPsbt btc ctx signer core.preparedV2)+      tx <- expectRight (psbtTransaction final)+      final.version `shouldBe` Version2+      verifyStdTx btc ctx tx [(coin, 5000000000, OutPoint "fede72ccadbf9f16f826e3078578b0660526a37b48d4f894ab26f4cf5a95e89c" 0)] `shouldBe` True++{- The checks of the signer and the finalizer -}++signerChecksSpec :: Ctx -> Spec+signerChecksSpec ctx =+  describe "The checks of the signer and the finalizer" $ do+    (secret, public) <- runIO (generate (arbitraryKeyPair ctx))+    (_, otherPublic) <- runIO (generate (arbitraryKeyPair ctx))+    let (psbt, coins) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+        parent = fromJust (either (const Nothing) id (nonWitnessUtxo (inputAt 0 psbt)))+        coin = head parent.outputs+        withInput m = withInputs [m] psbt+        signer = keySigner ctx secret.key+        signed m = signPsbt btc ctx signer (withInput (recordOf ctx public m))+        signatureCount s = fmap length (partialSignatures ctx (inputAt 0 s))++    it "refuses to sign a legacy coin known from its witness output only, which only a witness signature may use" $+      signed (insertWitnessUtxo coin Map.empty)+        `shouldBe` Left "Input 0: the coin is given as a witness output only, which may not be signed with a non-witness signature"++    it "takes the coin from the previous transaction first, and refuses a witness output that is not that output" $ do+      (signatureCount =<< signed (insertWitnessUtxo coin (insertNonWitnessUtxo parent Map.empty))) `shouldBe` Right 1+      signed (insertWitnessUtxo (TxOut 1 coin.script) (insertNonWitnessUtxo parent Map.empty))+        `shouldBe` Left "Input 0: The witness output of the input is not the output of its previous transaction"++    it "refuses a sighash type no node accepts, and one whose fork id flag does not fit the network" $ do+      signed (insertSigHashType 0 (insertNonWitnessUtxo parent Map.empty)) `shouldBe` Left "Input 0: the sighash type 0 is not one a node accepts"+      signed (insertSigHashType 0x21 (insertNonWitnessUtxo parent Map.empty)) `shouldBe` Left "Input 0: the sighash type 33 is not one a node accepts"+      signed (insertSigHashType (setForkIdFlag sigHashAll) (insertNonWitnessUtxo parent Map.empty)) `shouldBe` Left "Input 0: the sighash type has a fork id flag the network does not use"+      signPsbt bch ctx signer (withInput (recordOf ctx public (insertSigHashType sigHashAll (insertNonWitnessUtxo parent Map.empty))))+        `shouldBe` Left "Input 0: the sighash type lacks the fork id flag the network requires"+      -- With the flag, the Bitcoin Cash digest signs+      (signatureCount =<< signPsbt bch ctx signer (withInput (recordOf ctx public (insertSigHashType (setForkIdFlag sigHashAll) (insertNonWitnessUtxo parent Map.empty))))) `shouldBe` Right 1++    it "refuses a sighash type the signer does not accept, and signs with it once the signer lists it" $ do+      let acceptingSingle = signer {acceptedSigHashes = [sigHashSingle]}+          askingSingle = insertSigHashType sigHashSingle (insertNonWitnessUtxo parent Map.empty)+      signed askingSingle `shouldBe` Left "Input 0: the sighash type 3 is not one the signer accepts"+      (signatureCount =<< signPsbt btc ctx acceptingSingle (withInput (recordOf ctx public askingSingle))) `shouldBe` Right 1+      -- ANYONECANPAY makes another type: 0x81 is not 0x01+      signed (insertSigHashType (setAnyoneCanPay sigHashAll) (insertNonWitnessUtxo parent Map.empty)) `shouldBe` Left "Input 0: the sighash type 129 is not one the signer accepts"++    -- Two coins of the same script, spent by one transaction with one+    -- output: input 1 has no output at its index+    it "refuses SIGHASH_SINGLE on a legacy coin whose input has no output at its index, and signs it on a SegWit coin" $ do+      let witnessCoin = TxOut 200000000 (marshal ctx (PayWitnessPKHash (addressHash (marshal ctx public))))+          parentOf c = Tx 1 [TxIn (OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1) "" 0xffffffff] [c, c] [] 0+          spendingBoth p = Tx 2 [TxIn (OutPoint (txHash p) 0) "" 0xfffffffe, TxIn (OutPoint (txHash p) 1) "" 0xfffffffe] [TxOut 399999000 (hexBytes "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac")] [] 0+          askingSingle p = recordOf ctx public (insertSigHashType sigHashSingle (insertNonWitnessUtxo p Map.empty))+          psbtOf p = Psbt Version0 (spendingBoth p) Map.empty [askingSingle p, askingSingle p] [Map.empty]+          acceptingSingle = signer {acceptedSigHashes = [sigHashSingle]}+      signPsbt btc ctx acceptingSingle (psbtOf (parentOf coin)) `shouldBe` Left "Input 1: makeSignature: SIGHASH_SINGLE without an output at the input index"+      -- The BIP 143 digest commits to the coin, so the SegWit signature is fine+      (signatureCount =<< signPsbt btc ctx acceptingSingle (psbtOf (parentOf witnessCoin))) `shouldBe` Right 1+      (fmap length . partialSignatures ctx . inputAt 1 =<< signPsbt btc ctx acceptingSingle (psbtOf (parentOf witnessCoin))) `shouldBe` Right 1++    it "refuses a redeem script that does not hash to the coin, and one on a coin that is not pay-to-script-hash" $ do+      let ms = PayMulSig [public, otherPublic] 1+          msScript = either error id (encodeOutput ctx ms)+          wrongScript = either error id (encodeOutput ctx (PayMulSig [otherPublic, public] 1))+          (p2sh, _) = spendingPsbt ctx (toP2SH msScript)+          p2shParent = fromJust (either (const Nothing) id (nonWitnessUtxo (inputAt 0 p2sh)))+          p2shInput m = withInputs [recordOf ctx public (insertNonWitnessUtxo p2shParent m)] p2sh+      (signatureCount =<< signPsbt btc ctx signer (p2shInput (insertInputRedeemScript msScript Map.empty))) `shouldBe` Right 1+      signPsbt btc ctx signer (p2shInput (insertInputRedeemScript wrongScript Map.empty))+        `shouldBe` Left "Input 0: the redeem script of the input does not hash to its coin"+      signed (insertInputRedeemScript msScript (insertNonWitnessUtxo parent Map.empty))+        `shouldBe` Left "Input 0: the input has a redeem script, but its coin is not pay-to-script-hash"++    it "refuses a witness script that does not hash to the witness program" $ do+      let ms = PayMulSig [public, otherPublic] 1+          msScript = either error id (encodeOutput ctx ms)+          wrongScript = either error id (encodeOutput ctx (PayMulSig [otherPublic, public] 1))+          (p2wsh, _) = spendingPsbt ctx (toP2WSH msScript)+          p2wshInput m = withInputs [recordOf ctx public (Map.union m (inputAt 0 p2wsh))] p2wsh+      (signatureCount =<< signPsbt btc ctx signer (p2wshInput (insertInputWitnessScript msScript Map.empty))) `shouldBe` Right 1+      signPsbt btc ctx signer (p2wshInput (insertInputWitnessScript wrongScript Map.empty))+        `shouldBe` Left "Input 0: the witness script of the input does not hash to its witness program"+      signed (insertInputWitnessScript msScript (insertNonWitnessUtxo parent Map.empty))+        `shouldBe` Left "Input 0: the input has a witness script, but its coin is not pay-to-witness-script-hash"++    it "leaves an input whose coin script is not standard as it is, and signs the others" $ do+      let weird = TxOut 1000 (runPutS (serialize (Script [OP_1])))+          parent2 = Tx 1 parent.inputs [coin, weird] [] 0+          op0 = OutPoint (txHash parent2) 0+          op1 = OutPoint (txHash parent2) 1+          tx = Tx 2 [TxIn op0 "" 0xfffffffe, TxIn op1 "" 0xfffffffe] psbt.unsignedTx.outputs [] 0+          both = Psbt Version0 tx Map.empty [recordOf ctx public (insertNonWitnessUtxo parent2 Map.empty), insertNonWitnessUtxo parent2 Map.empty] [Map.empty, Map.empty]+      s <- expectRight (signPsbt btc ctx signer both)+      map hasPartialSignatures s.inputs `shouldBe` [True, False]+      f <- expectRight (finalizePsbt btc ctx s)+      map (isJust . finalScriptSig) f.inputs `shouldBe` [True, False]++    it "signs a coin whose key it holds without a derivation record, by the hash of the key" $ do+      (signatureCount =<< signPsbt btc ctx signer (withInput (insertNonWitnessUtxo parent Map.empty))) `shouldBe` Right 1+      -- A multisig coin names its keys+      let ms = PayMulSig [public, otherPublic] 1+          msScript = either error id (encodeOutput ctx ms)+          (p2sh, p2shCoins) = spendingPsbt ctx (toP2SH msScript)+          p2shParent = fromJust (either (const Nothing) id (nonWitnessUtxo (inputAt 0 p2sh)))+          p2shFields = withInputs [insertInputRedeemScript msScript (insertNonWitnessUtxo p2shParent Map.empty)] p2sh+      s <- expectRight (signPsbt btc ctx signer p2shFields)+      signatureCount s `shouldBe` Right 1+      signsAndVerifies btc ctx [secret] (p2shFields, p2shCoins) `shouldBe` Right True+      -- The other key is not held+      (signatureCount =<< signPsbt btc ctx (keySigner ctx secret.key) (fst (spendingPsbt ctx (PayPKHash (addressHash (marshal ctx otherPublic)))))) `shouldBe` Right 0+      coins `seq` return ()++    it "the finalizer refuses a signature that is not a signature, one with another sighash type than the input asks, and one that does not verify" $ do+      s <- expectRight (signed (insertNonWitnessUtxo parent Map.empty))+      signature <- case either error id (partialSignatures ctx (inputAt 0 s)) of+        [(_, sig)] -> return sig+        _ -> fail "one signature"+      let withSig sig m = withInput (insertPartialSignature ctx public sig m)+          keyHex = show (encodeHex (marshal ctx public))+      finalizePsbt btc ctx (withSig "3045aa" (insertNonWitnessUtxo parent Map.empty))+        `shouldBe` Left ("Input 0: the signature of the key " <> keyHex <> " is not a valid signature")+      finalizePsbt btc ctx (withSig signature (insertSigHashType sigHashNone (insertNonWitnessUtxo parent Map.empty)))+        `shouldBe` Left ("Input 0: the signature of the key " <> keyHex <> " does not use the sighash type of the input")+      -- The signature of the same key on another transaction+      (otherSecret, _) <- generate (arbitraryKeyPair ctx)+      let (otherPsbt, _) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx otherPublic)))+      otherSecret `seq` return ()+      let sameKeyOther = withInputs [recordOf ctx public (insertNonWitnessUtxo parent Map.empty)] (Psbt Version0 (Tx 2 psbt.unsignedTx.inputs [] [] 0) Map.empty psbt.inputs [])+      elsewhere <- expectRight (signPsbt btc ctx signer sameKeyOther)+      foreignSig <- case either error id (partialSignatures ctx (inputAt 0 elsewhere)) of+        [(_, sig)] -> return sig+        _ -> fail "one signature"+      finalizePsbt btc ctx (withSig foreignSig (insertNonWitnessUtxo parent Map.empty))+        `shouldBe` Left ("Input 0: the signature of the key " <> keyHex <> " does not verify")+      -- The right signature finalizes+      f <- expectRight (finalizePsbt btc ctx (withSig signature (insertNonWitnessUtxo parent Map.empty)))+      isJust (finalScriptSig (inputAt 0 f)) `shouldBe` True+      otherPsbt `seq` return ()++{- Taproot -}++data Vectors371 = Vectors371+  { refused371 :: [Vector],+    accepted371 :: [Vector]+  }++instance Json.FromJSON Vectors371 where+  parseJSON = Json.withObject "vectors" $ \o -> Vectors371 <$> o .: "invalid" <*> o .: "valid"++-- | Why each invalid PSBT of BIP 371 is refused, as the end of the reason+refusals371 :: [String]+refusals371 =+  [ "Input 0: the value of the key 0x17 is not an x-only public key",+    "Input 0: the value of the key 0x13 is not a Taproot signature: decodeTaprootSig: a signature is 64 or 65 bytes, not 63",+    "Input 0: the value of the key 0x13 is not a Taproot signature: decodeTaprootSig: a signature is 64 or 65 bytes, not 66",+    "is not an x-only public key",+    "Output 1: the value of the key 0x05 is not an x-only public key",+    "is not an x-only public key",+    "is not an x-only public key and a leaf hash",+    "is not a Taproot signature: decodeTaprootSig: a signature is 64 or 65 bytes, not 66",+    "is not a Taproot signature: decodeTaprootSig: a signature is 64 or 65 bytes, not 63",+    "is not a control block",+    "is not a control block"+  ]++taprootSpec :: Ctx -> Vectors371 -> Spec+taprootSpec ctx vectors =+  describe "The Taproot fields of BIP 371" $ do+    describe "the invalid PSBTs are refused, each for its reason" $+      forM_ (zip vectors.refused371 refusals371) $ \(v, reason) ->+        it v.vecCase $ decodePsbt (bytesOf v) `shouldSatisfy` either (reason `isSuffixOf`) (const False)++    describe "the valid PSBTs are read, and written back byte for byte" $+      forM_ vectors.accepted371 $ \v ->+        it v.vecCase $ (encodePsbt <$> decodePsbt (bytesOf v)) `shouldBe` Right (bytesOf v)++    let vectorAt i = psbtOfVector (vectors.accepted371 !! i)+        xOnly text = fromJust (importXOnlyPubKey ctx (hexBytes text))+        digest text = fromJust (digestFromByteString (hexBytes text)) :: Digest SHA256+        keyScript text = Script [opPushData (hexBytes text), OP_CHECKSIG]++    it "read the internal key and the derivation of a key path input" $ do+      psbt <- vectorAt 0+      inputTapInternalKey ctx (inputAt 0 psbt) `shouldBe` Right (Just (xOnly "fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232"))+      inputTapDerivations ctx (inputAt 0 psbt) `shouldBe` Right [(xOnly "fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232", TapDerivation [] (Derivation (fingerprintOf "772b2da7") "m/86'/1'/0'/1/0"))]+      inputTapMerkleRoot (inputAt 0 psbt) `shouldBe` Right Nothing+      tapKeySig (inputAt 0 psbt) `shouldBe` Right Nothing++    it "read the key path signature of a signed input, and finalize it into a witness of that signature" $ do+      psbt <- vectorAt 1+      let sigHex = "bb53ec917bad9d906af1ba87181c48b86ace5aae2b53605a725ca74625631476fc6f5baedaf4f2ee0f477f36f58f3970d5b8273b7e497b97af2e3f125c97af34"+          sig = fromJust (schnorrSig (hexBytes sigHex))+      tapKeySig (inputAt 0 psbt) `shouldBe` Right (Just (TaprootSignature sig sigHashDefault))+      final <- expectRight (finalizePsbt btc ctx psbt)+      finalScriptWitness (inputAt 0 final) `shouldBe` Right (Just [hexBytes sigHex])+      Map.keys (inputAt 0 final) `shouldBe` [Key 0x01 "", Key 0x08 ""]+      tx <- expectRight (psbtTransaction final)+      let coin = TxOut 5000000000 (hexBytes "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757")+      verifyStdTx btc ctx tx [(either error id (unmarshal ctx coin.script), coin.value, (head tx.inputs).outpoint)] `shouldBe` True++    it "read the internal key and the derivation of a key path output" $ do+      psbt <- vectorAt 2+      outputTapInternalKey ctx (outputAt 0 psbt) `shouldBe` Right (Just (xOnly "1124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e671"))+      outputTapDerivations ctx (outputAt 0 psbt) `shouldBe` Right [(xOnly "1124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e671", TapDerivation [] (Derivation (fingerprintOf "772b2da7") "m/86'/1'/0'/0/5"))]+      outputTapTree (outputAt 0 psbt) `shouldBe` Right Nothing++    it "read the leaf scripts, the derivations with their leaves, the internal key and the Merkle root of a script path input" $ do+      psbt <- vectorAt 3+      leaves <- expectRight (tapLeafScripts ctx (inputAt 0 psbt))+      map (.script) leaves+        `shouldBe` [ keyScript "2cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2",+                     keyScript "4320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2",+                     keyScript "fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9"+                   ]+      map (.leafVersion) leaves `shouldBe` [0xc0, 0xc0, 0xc0]+      map (B.length . (.controlBlock)) leaves `shouldBe` [97, 65, 97]+      inputTapInternalKey ctx (inputAt 0 psbt) `shouldBe` Right (Just (xOnly "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"))+      inputTapMerkleRoot (inputAt 0 psbt) `shouldBe` Right (Just (digest "f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65"))+      records <- expectRight (inputTapDerivations ctx (inputAt 0 psbt))+      lookup (xOnly "2cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2") records+        `shouldBe` Just (TapDerivation [digest "cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09"] (Derivation (fingerprintOf "772b2da7") "m/86'/1'/2'/0/0"))+      lookup (xOnly "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0") records+        `shouldBe` Just (TapDerivation [] (Derivation (fingerprintOf "7c461e5d") "m"))+      -- The leaf hash of the record is the hash of the leaf script+      leafHash 0xc0 (keyScript "2cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2") `shouldBe` digest "cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09"+      -- The control blocks commit the leaves to the output key, with the Merkle root+      let outKey = fromJust (importXOnlyPubKey ctx (hexBytes "c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b692"))+      forM_ leaves $ \leaf -> do+        (v, internal, proof) <- expectRight (decodeControlBlock ctx leaf.controlBlock)+        verifyScriptPathData ctx outKey (ScriptPathData Nothing [] leaf.script (odd v) 0xc0 internal proof) `shouldBe` True++    it "read the script tree of a script path output, whose leaves build the tree of its output key" $ do+      psbt <- vectorAt 4+      let leaves =+            [ TapLeaf 2 0xc0 (keyScript "736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02"),+              TapLeaf 2 0xc0 (keyScript "631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969"),+              TapLeaf 1 0xc0 (keyScript "44faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c4273")+            ]+      outputTapTree (outputAt 0 psbt) `shouldBe` Right (Just leaves)+      outputTapInternalKey ctx (outputAt 0 psbt) `shouldBe` Right (Just (xOnly "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"))+      let tree = fromJust (tapTreeFromLeaves leaves)+          output = TaprootOutput (xOnly "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0") (Just tree)+      marshal ctx (taprootScriptOutput ctx output) `shouldBe` (head psbt.unsignedTx.outputs).script+      Map.lookup (Key 0x06 "") (insertOutputTapTree leaves Map.empty) `shouldBe` Map.lookup (Key 0x06 "") (outputAt 0 psbt)+      records <- expectRight (outputTapDerivations ctx (outputAt 0 psbt))+      length records `shouldBe` 4++    it "read the script path signatures of a signed input, and finalize it with the first leaf that has one" $ do+      psbt <- vectorAt 5+      sigs <- expectRight (tapScriptSigs ctx (inputAt 0 psbt))+      map (\((k, h), s) -> (exportXOnlyPubKey ctx k, h, B.length (encodeTaprootSig s))) sigs+        `shouldBe` [ (hexBytes "2cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2", digest "cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09", 64),+                     (hexBytes "4320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2", digest "115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8", 64),+                     (hexBytes "fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9", digest "6f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970", 64)+                   ]+      final <- expectRight (finalizePsbt btc ctx psbt)+      witness <- expectRight (finalScriptWitness (inputAt 0 final))+      fmap (map B.length) witness `shouldBe` Just [64, 34, 97]+      tx <- expectRight (psbtTransaction final)+      let coin = TxOut 5000000000 (hexBytes "5120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b692")+      verifyStdTx btc ctx tx [(either error id (unmarshal ctx coin.script), coin.value, (head tx.inputs).outpoint)] `shouldBe` True++    it "write the fields back as they were read" $+      forM_ [0 .. 5] $ \i -> do+        psbt <- vectorAt i+        forM_ psbt.inputs $ \m -> do+          key <- expectRight (inputTapInternalKey ctx m)+          forM_ key $ \k -> Map.lookup (Key 0x17 "") (insertInputTapInternalKey ctx k Map.empty) `shouldBe` Map.lookup (Key 0x17 "") m+          root <- expectRight (inputTapMerkleRoot m)+          forM_ root $ \r -> Map.lookup (Key 0x18 "") (insertInputTapMerkleRoot r Map.empty) `shouldBe` Map.lookup (Key 0x18 "") m+          sig <- expectRight (tapKeySig m)+          forM_ sig $ \s -> Map.lookup (Key 0x13 "") (insertTapKeySig s Map.empty) `shouldBe` Map.lookup (Key 0x13 "") m+          leaves <- expectRight (tapLeafScripts ctx m)+          Map.toList (foldr insertTapLeafScript Map.empty leaves) `shouldBe` [(k, v) | (k, v) <- Map.toList m, k.keyType == 0x15]+          records <- expectRight (inputTapDerivations ctx m)+          Map.toList (foldr (uncurry (insertInputTapDerivation ctx)) Map.empty records) `shouldBe` [(k, v) | (k, v) <- Map.toList m, k.keyType == 0x16]+          sigs <- expectRight (tapScriptSigs ctx m)+          Map.toList (foldr (\((k, h), s) -> insertTapScriptSig ctx k h s) Map.empty sigs) `shouldBe` [(k, v) | (k, v) <- Map.toList m, k.keyType == 0x14]+        forM_ psbt.outputs $ \m -> do+          key <- expectRight (outputTapInternalKey ctx m)+          forM_ key $ \k -> Map.lookup (Key 0x05 "") (insertOutputTapInternalKey ctx k Map.empty) `shouldBe` Map.lookup (Key 0x05 "") m+          records <- expectRight (outputTapDerivations ctx m)+          Map.toList (foldr (uncurry (insertOutputTapDerivation ctx)) Map.empty records) `shouldBe` [(k, v) | (k, v) <- Map.toList m, k.keyType == 0x07]++    prop "a Taproot coin without script tree, signed by its key path with the internal key and finalized, verifies" $+      forAll (arbitraryKeyPair ctx) $ \(secret, public) ->+        let coinScript = taprootScriptOutput ctx (TaprootOutput public.point Nothing)+            (psbt, coins) = spendingPsbt ctx coinScript+            fields = insertInputTapInternalKey ctx public.point . insertInputTapDerivation ctx public.point (TapDerivation [] (Derivation (fingerprintOf "d90c6a4f") "m/86'/0'/0'/0/0"))+            withFields = withInputs (fields <$> psbt.inputs) psbt+            signed = signPsbt btc ctx (keySigner ctx secret.key) withFields+         in (signsAndVerifies btc ctx [secret] (withFields, coins), fmap (fmap (fmap (.hash)) . tapKeySig . inputAt 0) signed) === (Right True, Right (Right (Just sigHashDefault)))++    prop "a Taproot coin with a leaf that checks one key, signed by that leaf and finalized, verifies" $+      forAll ((,) <$> arbitraryKeyPair ctx <*> arbitraryKeyPair ctx) $ \((secret, public), (_, internal)) ->+        let leaf = Script [opPushData (exportXOnlyPubKey ctx public.point), OP_CHECKSIG]+            tree = MASTBranch (MASTLeaf 0xc0 leaf) (MASTLeaf 0xc0 (Script [OP_RETURN]))+            output = TaprootOutput internal.point (Just tree)+            coinScript = taprootScriptOutput ctx output+            (psbt, coins) = spendingPsbt ctx coinScript+            proof = case getMerkleProofs tree of+              (_, _, p) : _ -> p+              [] -> []+            control = encodeControlBlock ctx (0xc0 + keyParity ctx (taprootOutputKey ctx output)) internal.point proof+            fields =+              insertInputTapInternalKey ctx internal.point+                . insertInputTapMerkleRoot (mastCommitment tree)+                . insertTapLeafScript (TapLeafScript control leaf 0xc0)+                . insertInputTapDerivation ctx public.point (TapDerivation [leafHash 0xc0 leaf] (Derivation (fingerprintOf "d90c6a4f") "m/86'/0'/0'/0/1"))+            withFields = withInputs (fields <$> psbt.inputs) psbt+         in signsAndVerifies btc ctx [secret] (withFields, coins) === Right True++    it "the signer refuses a sighash type a Taproot signature may not use, and an internal key that does not give the coin" $ do+      (secret, public) <- generate (arbitraryKeyPair ctx)+      (_, other) <- generate (arbitraryKeyPair ctx)+      let coinScript = taprootScriptOutput ctx (TaprootOutput public.point Nothing)+          (psbt, _) = spendingPsbt ctx coinScript+          withFields m = withInputs [m (insertInputTapInternalKey ctx public.point (inputAt 0 psbt))] psbt+      signPsbt btc ctx (keySigner ctx secret.key) (withFields (insertSigHashType (setForkIdFlag sigHashAll))) `shouldBe` Left "Input 0: the sighash type 65 is not one a Taproot signature may use"+      signPsbt btc ctx (keySigner ctx secret.key) (withInputs [insertInputTapInternalKey ctx other.point (inputAt 0 psbt)] psbt) `shouldBe` Left "Input 0: the internal key and the Merkle root do not give the output key of the coin"+      -- Without the internal key, the input is left alone+      signPsbt btc ctx (keySigner ctx secret.key) psbt `shouldBe` Right psbt++{- Properties -}++propertiesSpec :: Ctx -> Spec+propertiesSpec ctx =+  describe "Any PSBT" $ do+    prop "is read back as it was written" $+      forAll (arbitraryPsbt btc ctx) $ \psbt -> decodePsbt (encodePsbt psbt) == Right psbt++    prop "is read back from its base64 text" $+      forAll (arbitraryPsbt btc ctx) $ \psbt -> decodePsbtBase64 (encodePsbtBase64 psbt) == Right psbt++    prop "combined with itself is itself" $+      forAll (arbitraryPsbt btc ctx) $ \psbt -> combinePsbt psbt psbt == Right psbt
test/Haskoin/Transaction/TaprootSpec.hs view
@@ -5,10 +5,14 @@ {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} +-- | Taproot: the script trees, output keys and control blocks of the BIP+-- 341 vectors, key path spending with its signature digest, the witness of+-- a spend, and the BIP 86 addresses. module Haskoin.Transaction.TaprootSpec (spec) where  import Control.Applicative ((<|>))-import Control.Monad (zipWithM, (<=<))+import Control.Monad (forM, forM_, zipWithM_, (<=<))+import Crypto.Hash (Digest, SHA256, digestFromByteString) import Data.Aeson import Data.Aeson.Types import Data.ByteArray qualified as BA@@ -17,29 +21,270 @@ import Data.Bytes.Get (runGetS) import Data.Bytes.Put (runPutS) import Data.Bytes.Serial (deserialize, serialize)+import Data.Either (isLeft)+import Data.Maybe (fromJust, isJust, isNothing)+import Data.Serialize qualified as S import Data.Text (Text)-import Data.Word (Word8)+import Data.Word (Word32, Word64) import Haskoin+import Haskoin.Util.Arbitrary import Test.HUnit (assertBool, (@?=))-import Test.Hspec (Spec, describe, it, runIO)+import Test.Hspec+import Test.Hspec.QuickCheck (prop)+import Test.QuickCheck  spec :: Spec spec = prepareContext $ \ctx -> do-  TestVector {testScriptPubKey} <-+  TestVector {testScriptPubKey, testKeyPath} <-     runIO $       readTestFileParser (testVectorParseJSON ctx) "bip341.json"-  describe "Taproot" $ do-    it "should calculate the correct hashes" $+  describe "Script trees and output keys, from the scriptPubKey vectors of BIP 341" $ do+    it "calculate the leaf hashes and the Merkle root" $       mapM_ testHashes testScriptPubKey-    it "should build the correct output key" $+    it "build the output key" $       mapM_ (testOutputKey ctx) testScriptPubKey-    it "should build the correct script output" $+    it "build the script output" $       mapM_ (testScriptOutput ctx) testScriptPubKey-    it "should calculate the correct control blocks" $+    it "build the control blocks, which verify the script path" $       mapM_ (testControlBlocks ctx) testScriptPubKey-    it "should arrive at the correct address" $+    it "give the address" $       mapM_ (testAddress ctx) testScriptPubKey+    it "list the leaves of a tree depth first and build the tree back from them" $+      mapM_ testTreeLeaves testScriptPubKey +  describe "The output key" $ do+    -- BIP 341 tweaks the x-only internal key, so the output does not+    -- depend on the parity of the y coordinate of the key given+    prop "is the same for a key and its negation" $+      forAll (arbitraryKeyPair ctx) $ \(_, p) ->+        let x = exportXOnlyPubKey ctx p.point+            negated = (.point) (either error id (unmarshal ctx (BS.cons (if keyParity ctx p.point == 0 then 0x03 else 0x02) x)) :: PublicKey)+         in taprootScriptOutput ctx (TaprootOutput negated Nothing) === taprootScriptOutput ctx (TaprootOutput p.point Nothing)++    prop "has the x coordinate of the public key of the output secret key" $+      forAll (arbitraryKeyPair ctx) $ \(k, p) ->+        let tweaked = fromJust (taprootOutputSecKey ctx k.key Nothing)+         in exportXOnlyPubKey ctx (derivePubKey ctx tweaked) === exportXOnlyPubKey ctx (taprootOutputKey ctx (TaprootOutput p.point Nothing))++    -- The test vectors of BIP 86: the keys at m/86'/0'/0'/0/0, 0/1 and+    -- 1/0 of the mnemonic "abandon ... about"+    forM_ bip86Vectors $ \(path, internalKey, outputKey, address) ->+      it ("of the BIP 86 key at " <> show path <> " is " <> show outputKey) $ do+        let root = fromJust (xPrvImport btc "xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu")+            secret = (derivePath ctx (path :: DerivPath) root).key+            output = TaprootOutput (derivePubKey ctx secret) Nothing+        encodeHex (exportXOnlyPubKey ctx (derivePubKey ctx secret)) `shouldBe` internalKey+        encodeHex (exportXOnlyPubKey ctx (taprootOutputKey ctx output)) `shouldBe` outputKey+        encodeHex (marshal ctx (taprootScriptOutput ctx output)) `shouldBe` "5120" <> outputKey+        (addrToText btc =<< outputAddress ctx (taprootScriptOutput ctx output)) `shouldBe` Just address+        -- The output secret key signs for the output key+        fmap (encodeHex . exportXOnlyPubKey ctx . derivePubKey ctx) (taprootOutputSecKey ctx secret Nothing) `shouldBe` Just outputKey++  describe "Key path spending, from the keyPathSpending vector of BIP 341" $ do+    forM_ testKeyPath.cases $ \c ->+      it ("input " <> show c.index <> ": the tweak, the digest and the signature, with hash type " <> show (fromIntegral c.hashType :: Word32)) $ do+        let output = TaprootOutput (derivePubKey ctx c.internalSecret) (MASTCommitment <$> c.merkleRoot)+            program = BS.drop 2 (testKeyPath.spent !! c.index).script+        encodeHex (exportXOnlyPubKey ctx (derivePubKey ctx c.internalSecret)) `shouldBe` c.internalPubKey+        exportXOnlyPubKey ctx (taprootOutputKey ctx output) `shouldBe` program+        let tweaked = fromJust (taprootOutputSecKey ctx c.internalSecret (MASTCommitment <$> c.merkleRoot))+        tweaked `shouldBe` fromJust (secKey (hexBytes c.tweakedSecret))+        digest <- expectRight (taprootSigHash testKeyPath.unsignedTx testKeyPath.spent c.index c.hashType Nothing Nothing)+        encodeHex (runPutS (serialize digest)) `shouldBe` c.sigHash+        -- The signature of the vector was made with 32 zero bytes of+        -- auxiliary randomness, as signSchnorr signs+        encodeHex (encodeTaprootSig (TaprootSignature (signSchnorr ctx tweaked (hashToMsg digest)) c.hashType)) `shouldBe` head c.witness++    it "the fully signed transaction of the vector verifies against the coins it spends" $+      verifyStdTx btc ctx testKeyPath.signedTx (coinsOf ctx testKeyPath) `shouldBe` True++    it "the fully signed transaction of the vector does not verify when a Taproot coin is given a wrong amount" $ do+      let coins = [(so, if i == (0 :: Int) then v + 1 else v, op) | (i, (so, v, op)) <- zip [0 ..] (coinsOf ctx testKeyPath)]+      verifyStdTx btc ctx testKeyPath.signedTx coins `shouldBe` False++    it "signTx signs the Taproot inputs of the vector by their key path with the internal keys, and the result verifies" $ do+      let tx = testKeyPath.unsignedTx+          coins = coinsOf ctx testKeyPath+          -- The internal keys of the Taproot inputs, with the key of the+          -- output as signTx finds it: the tweak of a key without a tree+          -- is the output only when the vector has no Merkle root+          noTree = [c | c <- testKeyPath.cases, isNothing c.merkleRoot]+          sigInputs = [SigInput so v op c.hashType Nothing | c <- noTree, let (so, v, op) = coins !! c.index]+          others = [SigInput so v op sigHashAll Nothing | (i, (so, v, op)) <- zip [0 ..] coins, i `notElem` map (.index) noTree]+      signed <- expectRight (signTx btc ctx tx (sigInputs <> others) (map (.internalSecret) noTree))+      forM_ noTree $ \c ->+        (signed.witness !! c.index) `shouldBe` [hexBytes (head c.witness)]++    it "taprootSigHash refuses an index out of range, a hash type a Taproot signature may not use, missing coins and SIGHASH_SINGLE without an output" $ do+      let tx = testKeyPath.unsignedTx+          spent = testKeyPath.spent+      taprootSigHash tx spent 9 sigHashDefault Nothing Nothing `shouldBe` Left "taprootSigHash: input index out of range"+      taprootSigHash tx spent (-1) sigHashDefault Nothing Nothing `shouldBe` Left "taprootSigHash: input index out of range"+      taprootSigHash tx (take 8 spent) 0 sigHashDefault Nothing Nothing `shouldBe` Left "taprootSigHash: one spent output per input is required"+      taprootSigHash tx spent 0 0x04 Nothing Nothing `shouldBe` Left "taprootSigHash: invalid hash type 4"+      taprootSigHash tx spent 0 (setForkIdFlag sigHashAll) Nothing Nothing `shouldBe` Left "taprootSigHash: invalid hash type 65"+      -- The transaction has two outputs: input 3 has none at its index+      taprootSigHash tx spent 3 sigHashSingle Nothing Nothing `shouldBe` Left "taprootSigHash: SIGHASH_SINGLE without an output at the input index"++    it "taprootSigHash commits to the annex and to the leaf of a script path spend" $ do+      let tx = testKeyPath.unsignedTx+          spent = testKeyPath.spent+          digest annex leaf = fmap (encodeHex . runPutS . serialize) (taprootSigHash tx spent 0 sigHashDefault annex leaf)+          leaf = leafHash tapscriptLeafVersion (Script [OP_1])+      digest Nothing Nothing `shouldNotBe` digest (Just "\x50") Nothing+      digest Nothing Nothing `shouldNotBe` digest Nothing (Just (leaf, 0xffffffff))+      digest Nothing (Just (leaf, 0xffffffff)) `shouldNotBe` digest Nothing (Just (leaf, 0))++  describe "Taproot signatures" $ do+    it "are 64 bytes with the default hash type, and 65 with another" $ do+      let sig = fromJust (schnorrSig (BS.replicate 64 7))+      encodeTaprootSig (TaprootSignature sig sigHashDefault) `shouldBe` BS.replicate 64 7+      encodeTaprootSig (TaprootSignature sig sigHashAll) `shouldBe` BS.replicate 64 7 <> "\x01"+      encodeTaprootSig (TaprootSignature sig (setAnyoneCanPay sigHashSingle)) `shouldBe` BS.replicate 64 7 <> "\x83"+      decodeTaprootSig (BS.replicate 64 7) `shouldBe` Right (TaprootSignature sig sigHashDefault)+      decodeTaprootSig (BS.replicate 64 7 <> "\x02") `shouldBe` Right (TaprootSignature sig sigHashNone)++    it "are refused with a hash type byte of zero, an undefined one, or another length" $ do+      decodeTaprootSig (BS.replicate 64 7 <> "\x00") `shouldBe` Left "decodeTaprootSig: invalid hash type byte 0"+      decodeTaprootSig (BS.replicate 64 7 <> "\x04") `shouldBe` Left "decodeTaprootSig: invalid hash type byte 4"+      decodeTaprootSig (BS.replicate 64 7 <> "\x41") `shouldBe` Left "decodeTaprootSig: invalid hash type byte 65"+      decodeTaprootSig (BS.replicate 63 7) `shouldBe` Left "decodeTaprootSig: a signature is 64 or 65 bytes, not 63"+      decodeTaprootSig (BS.replicate 66 7) `shouldBe` Left "decodeTaprootSig: a signature is 64 or 65 bytes, not 66"++    it "may use the default, ALL, NONE and SINGLE hash types, with ANYONECANPAY or not, and no other" $ do+      filter isTaprootSigHash [0 .. 0xff] `shouldBe` [0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83]+      sigHashDefault `shouldBe` 0++  describe "Witnesses" $ do+    let (_, p) = bip86Key ctx+        -- The internal key as a control block gives it back: the point of even y+        internalKey = fromJust (importXOnlyPubKey ctx (exportXOnlyPubKey ctx p.point))+        leaf = Script [OP_2DROP, OP_1]+        tree = MASTBranch (MASTLeaf tapscriptLeafVersion leaf) (MASTLeaf tapscriptLeafVersion (Script [OP_1]))+        output = TaprootOutput internalKey (Just tree)+        outputKey = taprootOutputKey ctx output+        [(_, _, proof), _] = getMerkleProofs tree+        spend = ScriptPathData {annex = Nothing, stack = ["aa", "bbbb"], script = leaf, extIsOdd = odd (keyParity ctx outputKey), leafVersion = tapscriptLeafVersion, internalKey, control = proof}+        control = encodeControlBlock ctx (tapscriptLeafVersion + keyParity ctx outputKey) internalKey proof+        scriptBytes = runPutS (serialize leaf)++    it "a key path spend is one signature, with the annex after it when there is one" $ do+      encodeTaprootWitness ctx (KeyPathSpend "sig" Nothing) `shouldBe` ["sig"]+      encodeTaprootWitness ctx (KeyPathSpend "sig" (Just "\x50\&annex")) `shouldBe` ["sig", "\x50\&annex"]+      viewTaprootWitness ctx ["sig"] `shouldBe` Just (KeyPathSpend "sig" Nothing)+      viewTaprootWitness ctx ["sig", "\x50\&annex"] `shouldBe` Just (KeyPathSpend "sig" (Just "\x50\&annex"))+      -- One item is the signature even when it starts with the annex byte+      viewTaprootWitness ctx ["\x50sig"] `shouldBe` Just (KeyPathSpend "\x50sig" Nothing)+      viewTaprootWitness ctx [] `shouldBe` Nothing++    it "a script path spend is the stack, the script and the control block, in the order of BIP 341, then the annex" $ do+      encodeTaprootWitness ctx (ScriptPathSpend spend) `shouldBe` ["aa", "bbbb", scriptBytes, control]+      encodeTaprootWitness ctx (ScriptPathSpend spend {annex = Just "\x50"}) `shouldBe` ["aa", "bbbb", scriptBytes, control, "\x50"]+      viewTaprootWitness ctx ["aa", "bbbb", scriptBytes, control] `shouldBe` Just (ScriptPathSpend spend)+      viewTaprootWitness ctx ["aa", "bbbb", scriptBytes, control, "\x50"] `shouldBe` Just (ScriptPathSpend spend {annex = Just "\x50"})+      viewTaprootWitness ctx [scriptBytes, control] `shouldBe` Just (ScriptPathSpend spend {stack = []})+      -- The control block and the script in the other order are not a+      -- spend: the script is not a control block+      viewTaprootWitness ctx [control, scriptBytes] `shouldBe` Nothing++    it "a script path spend verifies against its output key, and not with the other parity or another proof" $ do+      verifyScriptPathData ctx outputKey spend `shouldBe` True+      verifyScriptPathData ctx outputKey spend {extIsOdd = not spend.extIsOdd} `shouldBe` False+      verifyScriptPathData ctx outputKey spend {control = []} `shouldBe` False+      verifyScriptPathData ctx outputKey spend {script = Script [OP_1]} `shouldBe` False+      verifyScriptPathData ctx (taprootOutputKey ctx (TaprootOutput internalKey Nothing)) spend `shouldBe` False++    it "a control block is 33 bytes plus 32 per node, with at most 128 nodes" $ do+      let node = BS.replicate 32 1+          base = BS.take 33 control+          short = BS.take 20 control+          tooLong = control <> "junk"+          deepest = base <> BS.concat (replicate 128 node)+          tooDeep = base <> BS.concat (replicate 129 node)+      maxControlBlockNodes `shouldBe` 128+      fmap (\(v, k, nodes) -> (v, exportXOnlyPubKey ctx k == exportXOnlyPubKey ctx internalKey, length nodes)) (decodeControlBlock ctx deepest) `shouldBe` Right (tapscriptLeafVersion + keyParity ctx outputKey, True, 128)+      decodeControlBlock ctx short `shouldBe` Left "decodeControlBlock: a control block is 33 bytes plus 32 per node, not 20"+      decodeControlBlock ctx tooLong `shouldBe` Left "decodeControlBlock: a control block is 33 bytes plus 32 per node, not 69"+      decodeControlBlock ctx tooDeep `shouldBe` Left "decodeControlBlock: more than 128 nodes"+      viewTaprootWitness ctx [scriptBytes, short] `shouldBe` Nothing+      viewTaprootWitness ctx [scriptBytes, tooLong] `shouldBe` Nothing+      viewTaprootWitness ctx [scriptBytes, tooDeep] `shouldBe` Nothing+      fmap (\case ScriptPathSpend d -> length d.control; _ -> 0) (viewTaprootWitness ctx [scriptBytes, deepest]) `shouldBe` Just 128++    it "a control block starts with the leaf version and the parity bit, then the x-only internal key" $+      encodeHex (encodeControlBlock ctx 0xc1 p.point [])+        `shouldBe` "c1cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115"++    prop "encoding a witness and viewing it gives the witness back" $+      forAll ((,) <$> arbitraryBS1 <*> arbitraryMaybe arbitraryBS) $ \(sig, annexData) ->+        let annex = ("\x50" <>) <$> annexData+            spendWith a = ScriptPathSpend spend {annex = a}+         in (viewTaprootWitness ctx (encodeTaprootWitness ctx (KeyPathSpend sig annex)), viewTaprootWitness ctx (encodeTaprootWitness ctx (spendWith annex)))+              === (Just (KeyPathSpend sig annex), Just (spendWith annex))++  describe "Script trees as their leaves" $ do+    let a = MASTLeaf 0xc0 (Script [OP_1])+        b = MASTLeaf 0xc0 (Script [OP_2])+        c = MASTLeaf 0xc0 (Script [OP_3])+        leafOf d (MASTLeaf v s) = TapLeaf d v s+        leafOf _ _ = error "not a leaf"+    it "lists the leaves depth first with their depth" $ do+      tapTreeLeaves (MASTBranch (MASTBranch a b) c) `shouldBe` Just [leafOf 2 a, leafOf 2 b, leafOf 1 c]+      tapTreeLeaves a `shouldBe` Just [leafOf 0 a]+      tapTreeLeaves (MASTBranch a (MASTCommitment (mastCommitment b))) `shouldBe` Nothing+    it "builds the tree back from its leaves, with the same commitment" $ do+      let tree = MASTBranch (MASTBranch a b) c+      fmap mastCommitment (tapTreeFromLeaves [leafOf 2 a, leafOf 2 b, leafOf 1 c]) `shouldBe` Just (mastCommitment tree)+      fmap mastCommitment (tapTreeFromLeaves [leafOf 0 a]) `shouldBe` Just (mastCommitment a)+      fmap mastCommitment (tapTreeFromLeaves [leafOf 1 a, leafOf 2 b, leafOf 2 c]) `shouldBe` Just (mastCommitment (MASTBranch a (MASTBranch b c)))+    it "refuses leaves that do not make one tree" $ do+      tapTreeFromLeaves [] `shouldBe` Nothing+      tapTreeFromLeaves [leafOf 1 a] `shouldBe` Nothing+      tapTreeFromLeaves [leafOf 1 a, leafOf 1 b, leafOf 1 c] `shouldBe` Nothing+      tapTreeFromLeaves [leafOf 129 a] `shouldBe` Nothing++  describe "X-only keys" $ do+    it "serialize to the x coordinate, and read back as the point of even y" $ do+      let (_, p) = bip86Key ctx+          x = exportXOnlyPubKey ctx p.point+      marshal ctx (XOnlyPubKey p.point) `shouldBe` x+      fmap (keyParity ctx . (.point)) (unmarshal ctx x :: Either String XOnlyPubKey) `shouldBe` Right 0+      fmap (XOnlyPubKey p.point ==) (unmarshal ctx x) `shouldBe` Right True+      unmarshalJSON ctx (marshalJSON ctx (XOnlyPubKey p.point)) `shouldBe` Just (XOnlyPubKey p.point)+      (unmarshal ctx (BS.replicate 32 0xff) :: Either String XOnlyPubKey) `shouldSatisfy` isLeft+    it "the parity of a key is that of its y coordinate" $ do+      keyParity ctx (fromJust (importPubKey ctx (hexBytes "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))) `shouldBe` 0+      keyParity ctx (fromJust (importPubKey ctx (hexBytes "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))) `shouldBe` 1+    it "the Tapscript leaf version is 0xc0" $+      tapscriptLeafVersion `shouldBe` 0xc0++-- | The first key of the BIP 86 vectors, m/86'/0'/0'/0/0+bip86Key :: Ctx -> (PrivateKey, PublicKey)+bip86Key ctx = (PrivateKey secret True, PublicKey (derivePubKey ctx secret) True)+  where+    root = fromJust (xPrvImport btc "xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu")+    secret = (derivePath ctx ("m/86'/0'/0'/0/0" :: DerivPath) root).key++-- | The test vectors of BIP 86: path, internal key, output key, address+bip86Vectors :: [(DerivPath, Text, Text, Text)]+bip86Vectors =+  [ ("m/86'/0'/0'/0/0", "cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115", "a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c", "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"),+    ("m/86'/0'/0'/0/1", "83dfe85a3151d2517290da461fe2815591ef69f2b18a2ce63f01697a8b313145", "a82f29944d65b86ae6b5e5cc75e294ead6c59391a1edc5e016e3498c67fc7bbb", "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh"),+    ("m/86'/0'/0'/1/0", "399f1b2f4393f29a18c937859c5dd8a77350103157eb880f02e8c08214277cef", "882d74e5d0572d5a816cef0041a96b6c1de832f6f9676d9605c44d5e9a97d3dc", "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7")+  ]++hexBytes :: Text -> ByteString+hexBytes = fromJust . decodeHex++expectRight :: Either String a -> IO a+expectRight = either fail return++coinsOf :: Ctx -> KeyPathVector -> [(ScriptOutput, Word64, OutPoint)]+coinsOf ctx v =+  [ (either error id (unmarshal ctx coin.script), coin.value, input.outpoint)+  | (input, coin) <- zip v.unsignedTx.inputs v.spent+  ]+ testHashes :: TestScriptPubKey -> IO () testHashes testData =   mapM_ checkMASTDetails $ ((.mast) . tspkGiven) testData@@ -74,8 +319,10 @@     theOutput = tspkGiven testData     theOutputKey = taprootOutputKey ctx theOutput     exampleControlBlocks = spkeControlBlocks $ tspkExpected testData+    -- The control block is the last item of the witness: the stack is+    -- empty, so it comes after the script     calculatedControlBlocks =-      (!! 1) . encodeTaprootWitness ctx . ScriptPathSpend <$> scriptPathSpends+      last . encodeTaprootWitness ctx . ScriptPathSpend <$> scriptPathSpends     scriptPathSpends =       mkScriptPathSpend <$> maybe mempty getMerkleProofs theOutput.mast     mkScriptPathSpend (leafVersion, script, proof) =@@ -86,17 +333,11 @@           extIsOdd = odd $ keyParity ctx theOutputKey,           leafVersion,           internalKey = theOutput.internalKey,-          control = BA.convert <$> proof+          control = proof         }-    onExamples = zipWithM (@?=) calculatedControlBlocks+    onExamples = zipWithM_ (@?=) calculatedControlBlocks     checkVerification = assertBool "Script verifies" . verifyScriptPathData ctx theOutputKey -keyParity :: Ctx -> PubKey -> Word8-keyParity ctx key =-  case BS.unpack . marshal ctx $ PublicKey key True of-    0x02 : _ -> 0x00-    _ -> 0x01- testAddress :: Ctx -> TestScriptPubKey -> IO () testAddress ctx testData =   computedAddress @?= (Just . spkeAddress . tspkExpected) testData@@ -106,6 +347,13 @@         . taprootScriptOutput ctx         $ tspkGiven testData +testTreeLeaves :: TestScriptPubKey -> IO ()+testTreeLeaves testData =+  forM_ (tspkGiven testData).mast $ \theMAST -> do+    let leaves = tapTreeLeaves theMAST+    leaves `shouldSatisfy` isJust+    fmap mastCommitment (tapTreeFromLeaves =<< leaves) `shouldBe` Just (mastCommitment theMAST)+ newtype SpkGiven = SpkGiven {unSpkGiven :: TaprootOutput}  spkGivenParseJSON :: Ctx -> Value -> Parser SpkGiven@@ -169,13 +417,64 @@   expect <- spkExpectedParseJSON ctx =<< obj .: "expected"   return $ TestScriptPubKey given inter expect -newtype TestVector = TestVector-  { testScriptPubKey :: [TestScriptPubKey]+-- | The keyPathSpending vector: the unsigned transaction, the outputs it+-- spends, one case per Taproot input, and the fully signed transaction+data KeyPathVector = KeyPathVector+  { unsignedTx :: Tx,+    spent :: [TxOut],+    cases :: [KeyPathCase],+    signedTx :: Tx   } +data KeyPathCase = KeyPathCase+  { index :: Int,+    internalSecret :: SecKey,+    merkleRoot :: Maybe (Digest SHA256),+    hashType :: SigHash,+    internalPubKey :: Text,+    tweakedSecret :: Text,+    sigHash :: Text,+    witness :: [Text]+  }++keyPathParseJSON :: Value -> Parser KeyPathVector+keyPathParseJSON = withObject "keyPathSpending" $ \obj -> do+  given <- obj .: "given"+  rawTx <- given .: "rawUnsignedTx" >>= jsonHex+  tx <- either fail return (S.decode rawTx)+  utxos <- given .: "utxosSpent"+  spent <- forM utxos $ \u -> TxOut <$> u .: "amountSats" <*> (u .: "scriptPubKey" >>= jsonHex)+  inputCases <- obj .: "inputSpending"+  cases <- forM inputCases $ \c -> do+    g <- c .: "given"+    i <- c .: "intermediary"+    e <- c .: "expected"+    secretBytes <- g .: "internalPrivkey" >>= jsonHex+    rootM <- g .: "merkleRoot" >>= traverse jsonHex+    KeyPathCase+      <$> g .: "txinIndex"+      <*> maybe (fail "internal private key") return (secKey secretBytes)+      <*> pure (digestFromByteString =<< rootM)+      <*> (fromIntegral <$> (g .: "hashType" :: Parser Word32))+      <*> i .: "internalPubkey"+      <*> i .: "tweakedPrivkey"+      <*> i .: "sigHash"+      <*> e .: "witness"+  aux <- obj .: "auxiliary"+  signedRaw <- aux .: "fulledSignedTx" >>= jsonHex+  signed <- either fail return (S.decode signedRaw)+  return (KeyPathVector tx spent cases signed)++data TestVector = TestVector+  { testScriptPubKey :: [TestScriptPubKey],+    testKeyPath :: KeyPathVector+  }+ testVectorParseJSON :: Ctx -> Value -> Parser TestVector testVectorParseJSON ctx = withObject "TestVector" $ \obj ->-  TestVector <$> (mapM (testScriptPubKeyParseJSON ctx) =<< obj .: "scriptPubKey")+  TestVector+    <$> (mapM (testScriptPubKeyParseJSON ctx) =<< obj .: "scriptPubKey")+    <*> (obj .: "keyPathSpending" >>= \case [v] -> keyPathParseJSON v; _ -> fail "one keyPathSpending vector")  jsonHex :: Text -> Parser ByteString jsonHex = maybe (fail "Unable to decode hex") pure . decodeHex
test/Haskoin/TransactionSpec.hs view
@@ -5,19 +5,27 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} +-- | Transactions: their ids, their bytes and sizes, building, signing and+-- verifying them, merging partial signatures, coin selection and fee+-- estimation, the SegWit witness helpers and the genesis coinbases. module Haskoin.TransactionSpec (spec) where -import Control.Monad (unless)+import Control.Exception (evaluate)+import Control.Monad (forM_, guard, unless)+import Data.Aeson (decode, eitherDecode)+import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Default (def) import Data.Either+import Data.List (find, inits, isInfixOf) import Data.Maybe import Data.String (fromString) import Data.String.Conversions import Data.Text (Text)+import Data.Text qualified as T import Data.Word (Word32, Word64) import Haskoin.Address import Haskoin.Crypto@@ -31,6 +39,7 @@ import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck+import Text.Read (readMaybe)  identityTests :: Ctx -> IdentityTests identityTests ctx =@@ -74,16 +83,17 @@     prop "building address tx" $       forAll arbitraryNetAddress $ \(net, addr) ->         forAll (arbitrarySatoshi net) $ testBuildAddrTx net ctx addr-    prop "guess transaction size" $+    prop "the size guess is at least the virtual size of a signed legacy transaction, with little slack" $       forAll arbitraryNetwork $ \net ->         forAll (arbitraryAddrOnlyTxFull net ctx) (testGuessSize net ctx)+    prop "the weight guess is at least the weight of a signed transaction whose inputs have a kind" $+      forAll arbitraryNetwork $ \net -> testGuessWeight net ctx False+    prop "the weight guess is at least the weight of a signed transaction with its SegWit inputs nested" $+      forAll arbitraryNetwork $ \net -> testGuessWeight net ctx True     prop "choose coins" $       forAll arbitraryNetwork $ \net ->-        forAll (listOf (arbitrarySatoshi net)) testChooseCoins-    prop "choose multisig coins" $-      forAll arbitraryNetwork $ \net ->-        forAll arbitraryMSParam $-          forAll (listOf (arbitrarySatoshi net)) . testChooseMSCoins+        forAll (listOf (arbitrarySatoshi net)) $ \coins ->+          forAll (listOf arbitraryOutputKind) (testChooseCoins coins)     prop "sign and validate transaction" $       forAll arbitraryNetwork $ \net ->         forAll (arbitrarySigningData net ctx) (testDetSignTx net ctx)@@ -93,10 +103,23 @@     prop "merge partially signed transactions" $       forAll arbitraryNetwork $ \net ->         property $ forAll (arbitraryPartialTxs net ctx) (testMergeTx net ctx)+    prop "nosigTxHash does not change when a transaction is signed, whether in the input scripts or in the witness" $+      forAll arbitraryNetwork $ \net ->+        forAll (arbitrarySigningData net ctx) $ \(tx, sigis, prv) ->+          nosigTxHash (either error id (signTx net ctx tx sigis (map (.key) prv))) === nosigTxHash tx   describe "Transaction vectors" $ do     it "compute txid from tx" $ mapM_ testTxidVector txidVectors     it "build pkhash transaction (generated from bitcoind)" $       mapM_ (testPKHashVector ctx) pkHashVectors+  idsSpec+  bytesSpec ctx+  sizeSpec ctx+  coinSelectionSpec+  verificationSpec ctx+  mergeSpec ctx+  taprootSigningSpec ctx+  segwitSpec ctx+  genesisSpec  -- Txid Vectors @@ -226,68 +249,102 @@ -- Transaction Properties --  testBuildAddrTx :: Network -> Ctx -> Address -> TestCoin -> IO ()-testBuildAddrTx net ctx a (TestCoin v)+testBuildAddrTx net ctx a coin   | isPubKeyAddress a = (PayPKHash <$> addressHash160 a) `shouldBe` Just out   | isScriptAddress a = (PayScriptHash <$> addressHash160 a) `shouldBe` Just out   | otherwise = discard   where     out = either error id $ do-      tx <- buildAddrTx net ctx [] [(fromJust (addrToText net a), v)]+      tx <- buildAddrTx net ctx [] [(fromJust (addrToText net a), coin.getTestCoin)]       unmarshal ctx (head tx.outputs).script --- We compute an upper bound but it should be close enough to the real size--- We give 2 bytes of slack on every signature (1 on r and 1 on s)+-- The guess is an upper bound on the virtual size that counts 72 bytes+-- per signature. A signature with a short r or s is 1 or 2 bytes less. testGuessSize :: Network -> Ctx -> Tx -> Bool testGuessSize net ctx tx =-  guess >= len && guess <= len + 2 * delta+  guess >= size && guess <= size + 2 * signatures   where-    delta = pki + sum (map fst msi)-    guess = guessTxSize pki msi pkout msout-    len = B.length $ runPutS $ serialize tx-    ins = map f tx.inputs-    f i = either error id $ unmarshal (net, ctx) i.script-    pki = length $ filter isSpendPKHash ins-    msi = concatMap shData ins-    shData (ScriptHashInput _ (PayMulSig keys r)) = [(r, length keys)]-    shData _ = []-    out = map (either error id . unmarshal ctx . (.script)) tx.outputs-    pkout = length $ filter isPayPKHash out-    msout = length $ filter isPayScriptHash out+    guess = guessTxSize (map inputKind ins) (map outputKind outs)+    size = txVirtualSize tx+    ins = map (either error id . unmarshal (net, ctx) . (.script)) tx.inputs+    outs = map (either error id . unmarshal ctx . (.script)) tx.outputs+    inputKind (ScriptHashInput _ (PayMulSig keys r)) = InputMulSig r (length keys)+    inputKind _ = InputPKHash+    outputKind o+      | isPayPKHash o = OutputPKHash+      | otherwise = OutputScriptHash+    signatures = sum (map sigCount ins)+    sigCount (ScriptHashInput (SpendMulSig sigs) _) = length sigs+    sigCount _ = 1 -testChooseCoins :: [TestCoin] -> Word64 -> Word64 -> Int -> Property-testChooseCoins coins target byteFee nOut =-  nOut >= 0 ==>-    case chooseCoins target byteFee nOut True coins of-      Right (chosen, change) ->-        let outSum = sum $ map coinValue chosen-            fee = guessTxFee byteFee nOut (length chosen)-         in outSum == target + change + fee-      Left _ ->-        let fee = guessTxFee byteFee nOut (length coins)-         in target == 0 || s < target + fee+-- The guess is an upper bound on the weight of the transaction once signed:+-- by signTx, or by signNestedWitnessTx with its SegWit inputs nested. Only+-- signing data whose inputs all have a kind is generated.+testGuessWeight :: Network -> Ctx -> Bool -> Property+testGuessWeight net ctx nested =+  forAll (arbitrarySigningData net ctx `suchThat` allKnown) $ \(tx, sigis, keys) ->+    let signed = either error id (sign net ctx tx sigis (map (.key) keys))+        kinds = mapMaybe (sigInputKind ctx nested keys) sigis+        guess = guessTxWeight kinds (map (txOutKind ctx) tx.outputs)+     in counterexample ("weight " <> show (txWeight signed) <> ", guess " <> show guess) $+          txWeight signed <= guess   where-    s = sum $ map coinValue coins+    sign = if nested then signNestedWitnessTx else signTx+    allKnown (_, sigis, keys) = all (isJust . sigInputKind ctx nested keys) sigis -testChooseMSCoins ::-  (Int, Int) ->-  [TestCoin] ->-  Word64 ->-  Word64 ->-  Int ->-  Property-testChooseMSCoins (m, n) coins target byteFee nOut =-  nOut >= 0 ==>-    case chooseMSCoins target byteFee (m, n) nOut True coins of-      Right (chosen, change) ->-        let outSum = sum $ map coinValue chosen-            fee = guessMSTxFee byteFee (m, n) nOut (length chosen)-         in outSum == target + change + fee-      Left _ ->-        let fee = guessMSTxFee byteFee (m, n) nOut (length coins)-         in target == 0 || s < target + fee+-- The kind of input a SigInput is signed as, when the estimator has one.+-- The key of a public key hash is found among the signing keys, to tell+-- whether it is compressed. These are left out, since the estimator has no+-- kind for them: bare multisig, pay-to-script-hash and+-- pay-to-witness-script-hash of a single key, multisig with an uncompressed+-- key, and pay-to-witness-public-key-hash with an uncompressed key, which+-- the estimator counts as compressed.+sigInputKind :: Ctx -> Bool -> [PrivateKey] -> SigInput -> Maybe InputKind+sigInputKind ctx nested keys si = case (si.script, si.redeem) of+  (PayPK _, Nothing) -> Just InputPK+  (PayPKHash h, Nothing) -> do+    k <- keyOfHash h+    Just $ if k.compress then InputPKHash else InputUncompressedPKHash+  (PayScriptHash _, Just (PayMulSig ps m)) -> compressedMultisig InputMulSig ps m+  (PayWitnessPKHash h, Nothing) -> do+    k <- keyOfHash h+    guard k.compress+    Just $ if nested then InputNestedWitnessPKHash else InputWitnessPKHash+  (PayWitnessScriptHash _, Just (PayMulSig ps m)) ->+    compressedMultisig (if nested then InputNestedWitnessMulSig else InputWitnessMulSig) ps m+  (PayWitness 1 program, Nothing) | B.length program == 32 -> Just InputTaproot+  _ -> Nothing   where-    s = sum $ map coinValue coins+    keyOfHash h = find (\k -> addressHash160 (pubKeyAddr ctx (derivePublicKey ctx k)) == Just h) keys+    compressedMultisig kind ps m = do+      guard (all (.compress) ps)+      Just (kind m (length ps)) +-- The kind of an output, from its script. A script of no named kind is+-- 'OutputScript' with its size.+txOutKind :: Ctx -> TxOut -> OutputKind+txOutKind ctx out = case unmarshal ctx out.script of+  Right (PayPKHash _) -> OutputPKHash+  Right (PayScriptHash _) -> OutputScriptHash+  Right (PayScript32Hash _) -> OutputScript32Hash+  Right (PayWitnessPKHash _) -> OutputWitnessPKHash+  Right (PayWitnessScriptHash _) -> OutputWitnessScriptHash+  Right (PayWitness 1 program) | B.length program == 32 -> OutputTaproot+  _ -> OutputScript (B.length out.script)++-- A selection covers the target, the fee of the coins chosen, and the+-- change. When there is no selection, no prefix of the coins covers the+-- target and its own fee, since the coins are taken in order.+testChooseCoins :: [TestCoin] -> [OutputKind] -> Word64 -> Word64 -> Property+testChooseCoins coins outs target byteFee =+  case chooseCoins target byteFee outs True coins of+    Right (chosen, change) ->+      sum (map coinValue chosen) === target + change + fee chosen+    Left _ ->+      property $ target == 0 || all (\prefix -> sum (map coinValue prefix) < target + fee prefix) (drop 1 (inits coins))+  where+    fee cs = guessTxFee byteFee (map coinInputKind cs) outs+ {- Signing Transactions -}  testDetSignTx :: Network -> Ctx -> (Tx, [SigInput], [PrivateKey]) -> Bool@@ -317,7 +374,7 @@         signNestedWitnessTx net ctx txSigP sigis [(head prv).key]     verData = handleSegwit <$> sigis     handleSegwit (SigInput s v o _ _)-      | isSegwit s = (toP2SH (encodeOutput ctx s), v, o)+      | isSegwit s = (toP2SH (either error id (encodeOutput ctx s)), v, o)       | otherwise = (s, v, o)  testMergeTx :: Network -> Ctx -> ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)]) -> Bool@@ -344,3 +401,518 @@         Right (RegularInput (SpendMulSig sigs)) -> length sigs         Right (ScriptHashInput (SpendMulSig sigs) _) -> length sigs         _ -> error "Invalid input script type"++{- Ids -}++-- The id of the genesis coinbase, in display order+genesisTxId :: Text+genesisTxId = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"++-- The signed transaction of the P2WPKH example of BIP 143: 343 bytes, 233+-- without its witness+bip143SignedTx :: Text+bip143SignedTx =+  "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"++hexBytes :: Text -> ByteString+hexBytes = fromJust . decodeHex++parseTx :: Text -> Either String Tx+parseTx = runGetS deserialize . hexBytes++idsSpec :: Spec+idsSpec =+  describe "Transaction ids" $ do+    it "hexToTxHash reads 32 bytes of hex in display order, in either case" $ do+      fmap (encodeHex . runPutS . serialize) (hexToTxHash genesisTxId)+        `shouldBe` Just "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"+      hexToTxHash "4A5E1E4BAAB89F3A32518A88C31BC87F618F76673E2CC77AB2127B7AFDEDA33B" `shouldBe` hexToTxHash genesisTxId+      fmap txHashToHex (hexToTxHash genesisTxId) `shouldBe` Just genesisTxId++    it "hexToTxHash refuses hex that is not exactly 32 bytes, or not hex" $ do+      hexToTxHash (genesisTxId <> "ff") `shouldBe` Nothing+      hexToTxHash (T.take 62 genesisTxId) `shouldBe` Nothing+      hexToTxHash "" `shouldBe` Nothing+      hexToTxHash "zz5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" `shouldBe` Nothing++    it "the Read, string and JSON forms of a transaction id refuse 33 bytes" $ do+      (readMaybe (show (cs (genesisTxId <> "ff") :: String)) :: Maybe TxHash) `shouldBe` Nothing+      (decode (cs ("\"" <> genesisTxId <> "ff\"")) :: Maybe TxHash) `shouldBe` Nothing+      (decode (cs ("{\"txid\":\"" <> genesisTxId <> "ff\",\"index\":0}")) :: Maybe OutPoint) `shouldBe` Nothing+      (decode (cs ("{\"txid\":\"" <> genesisTxId <> "\",\"index\":0}")) :: Maybe OutPoint) `shouldBe` Just (OutPoint (fromJust (hexToTxHash genesisTxId)) 0)++    it "the witness transaction id of the BIP 143 example is the hash of all its bytes, and its id leaves the witness out" $ do+      tx <- either fail return (parseTx bip143SignedTx)+      txHashToHex (txHash tx) `shouldBe` "e8151a2af31c368a35053ddd4bdb285a8595c769a3ad83e0fa02314a602d4609"+      txHashToHex (txWitnessHash tx) `shouldBe` "c36c38370907df2324d9ce9d149d191192f338b37665a82e78e76a12c909b762"++    it "the witness transaction id of a transaction without witness is its id" $+      txWitnessHash mainnetGenesisTx `shouldBe` txHash mainnetGenesisTx++    -- The genesis coinbase with its input script removed hashes to+    -- 2a9487e4…78c1, which is neither its id nor the hash of its script+    it "nosigTxHash is the hash of the transaction with its input scripts and witness removed" $ do+      txHashToHex (nosigTxHash mainnetGenesisTx) `shouldBe` "2a9487e467412bc4b0a993a770bc73d5f4230f0ea5907b3f5df2669f0d7e78c1"+      tx <- either fail return (parseTx bip143SignedTx)+      let unsigned = Tx tx.version [TxIn i.outpoint "" i.sequence | i <- tx.inputs] tx.outputs [] tx.locktime+      nosigTxHash tx `shouldBe` nosigTxHash unsigned+      nosigTxHash unsigned `shouldBe` txHash unsigned++{- Bytes -}++bytesSpec :: Ctx -> Spec+bytesSpec ctx =+  describe "Transaction bytes" $ do+    -- The bytes of the first pkhash vector, with the input count written+    -- as 0xffffffffffffffff, which wraps around to -1 as an Int+    it "refuse a count that is beyond the limit of a list" $ do+      parseTx ("01000000" <> "ffffffffffffffffff" <> "02" <> "804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "00000000")+        `shouldSatisfy` either ("List count too large" `isInfixOf`) (const False)+      parseTx ("01000000" <> "0001" <> "01db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "ffffffffffffffffff" <> "00000000")+        `shouldSatisfy` either ("List count too large" `isInfixOf`) (const False)+      parseTx ("01000000" <> "01db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e000000" <> "ffffffffffffffffff" <> "ffffffff" <> "01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "00000000")+        `shouldSatisfy` either ("Byte count too large" `isInfixOf`) (const False)++    it "refuse the witness marker on a transaction whose witness is empty, as Bitcoin Core does" $ do+      parseTx ("01000000" <> "0001" <> "01db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "00" <> "00000000")+        `shouldSatisfy` either ("Superfluous witness record" `isInfixOf`) (const False)+      -- No inputs, so no witness at all+      parseTx ("01000000" <> "0001" <> "00" <> "01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "00000000")+        `shouldSatisfy` either ("Superfluous witness record" `isInfixOf`) (const False)++    -- An input count of zero makes the next byte the flags of BIP 144+    it "refuse a flags byte other than the witness bit after an empty input list, as Bitcoin Core does" $ do+      parseTx ("01000000" <> "00" <> "02" <> "02e80300000000000000e8030000000000000000000000")+        `shouldSatisfy` either ("Unknown transaction optional data" `isInfixOf`) (const False)+      parseTx ("01000000" <> "00" <> "03" <> "01db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac" <> "00" <> "00000000")+        `shouldSatisfy` either ("Unknown transaction optional data" `isInfixOf`) (const False)++    it "read an empty input list with a zero flags byte as a transaction with no inputs and no outputs, as Bitcoin Core does" $+      parseTx ("01000000" <> "00" <> "00" <> "00000000") `shouldBe` Right (Tx 1 [] [] [] 0)++    it "refuse a literal with bytes after the transaction" $ do+      let legacy = "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000"+      length (fromString legacy :: Tx).inputs `shouldBe` 1+      evaluate (fromString (legacy <> "00") :: Tx) `shouldThrow` errorCall "Could not read transaction from hex string"++    it "write a transaction whose witness stacks are all empty without the marker, and read it back without witness" $ do+      let legacy = "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000"+      tx <- either fail return (parseTx legacy)+      hasWitness tx `shouldBe` False+      hasWitness tx {witness = [[]]} `shouldBe` False+      hasWitness tx {witness = [["x"]]} `shouldBe` True+      encodeHex (runPutS (serialize tx {witness = [[]]})) `shouldBe` legacy+      runGetS deserialize (runPutS (serialize tx {witness = [[]]})) `shouldBe` Right tx+      txWitnessHash tx {witness = [[]]} `shouldBe` txHash tx++    it "write one witness stack per input" $ do+      let legacy = "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000"+      tx <- either fail return (parseTx legacy)+      -- A second stack has no input to go with+      fmap (.witness) (runGetS deserialize (runPutS (serialize tx {witness = [["ab"], ["cd"]]})) :: Either String Tx) `shouldBe` Right [["ab"]]++    it "give the weight and the virtual size of BIP 141: the BIP 143 example and the genesis coinbase" $ do+      tx <- either fail return (parseTx bip143SignedTx)+      -- 3 × 233 + 343 = 1042 weight units, 260.5 rounded up+      txWeight tx `shouldBe` 1042+      txVirtualSize tx `shouldBe` 261+      -- 204 bytes without witness: four times the size+      txWeight mainnetGenesisTx `shouldBe` 816+      txVirtualSize mainnetGenesisTx `shouldBe` 204++    it "the JSON form of an output refuses a negative amount" $ do+      (eitherDecode "{\"value\":-1,\"outputscript\":\"\"}" :: Either String TxOut) `shouldSatisfy` isLeft+      (eitherDecode "{\"value\":5000000000,\"outputscript\":\"51\"}" :: Either String TxOut) `shouldBe` Right (TxOut 5000000000 "\x51")++{- Sizes and fees -}++sizeSpec :: Ctx -> Spec+sizeSpec ctx =+  describe "Size and fee estimation" $ do+    it "counts the bytes of each kind of input, without and with witness" $ do+      -- outpoint 36, sequence 4, script length 1, signature push 73+      inputWeight InputPK `shouldBe` (114, 0)+      -- outpoint 36, sequence 4, script length 1, signature push 73, key push 34+      inputWeight InputPKHash `shouldBe` (148, 0)+      -- the same with a 65-byte key: push 66+      inputWeight InputUncompressedPKHash `shouldBe` (180, 0)+      -- an empty script; the witness: count 1, signature 73, key 34+      inputWeight InputWitnessPKHash `shouldBe` (41, 108)+      -- the push of the 22-byte program: 23 bytes after their length+      inputWeight InputNestedWitnessPKHash `shouldBe` (64, 108)+      -- the witness: count 1, a 65-byte signature after its length+      inputWeight InputTaproot `shouldBe` (41, 67)+      -- OP_0, two signatures of 73, the 105-byte script pushed with OP_PUSHDATA1 (107): 254 bytes, after a 3-byte length+      inputWeight (InputMulSig 2 3) `shouldBe` (297, 0)+      -- the witness: count 1, the empty item 1, two signatures of 73, the script after its length 106+      inputWeight (InputWitnessMulSig 2 3) `shouldBe` (41, 254)+      -- the push of the 34-byte program: 35 bytes after their length+      inputWeight (InputNestedWitnessMulSig 2 3) `shouldBe` (76, 254)+      -- OP_0, one signature, the 37-byte script pushed directly (38): 112 bytes+      inputWeight (InputMulSig 1 1) `shouldBe` (153, 0)++    it "counts two bytes for each count of a multisig script above 16" $ do+      -- 17-of-20: OP_m 2, 20 keys of 34 (680), OP_n 2, OP_CHECKMULTISIG 1: a 685-byte script.+      -- OP_0, 17 signatures of 73 (1241), the script pushed with OP_PUSHDATA2 (688): 1930 bytes after a 3-byte length, and 40+      inputWeight (InputMulSig 17 20) `shouldBe` (1973, 0)+      -- the witness: count 1, the empty item 1, 17 signatures of 73 (1241), the 685-byte script after its 3-byte length (688)+      inputWeight (InputWitnessMulSig 17 20) `shouldBe` (41, 1931)+      inputWeight (InputNestedWitnessMulSig 17 20) `shouldBe` (76, 1931)+      -- 1-of-17: OP_1 1, 17 keys (578), a two-byte count, OP_CHECKMULTISIG: 582 bytes.+      -- OP_0, one signature (73), OP_PUSHDATA2 (585): 659 bytes after a 3-byte length, and 40+      inputWeight (InputMulSig 1 17) `shouldBe` (702, 0)+      -- 16-of-16 keeps one-byte counts: 1 + 544 + 1 + 1 = 547 bytes.+      -- OP_0, 16 signatures (1168), OP_PUSHDATA2 (550): 1719 bytes after a 3-byte length, and 40+      inputWeight (InputMulSig 16 16) `shouldBe` (1762, 0)++    it "counts the bytes of each kind of output" $ do+      outputSize OutputPKHash `shouldBe` 34+      outputSize OutputScriptHash `shouldBe` 32+      -- amount 8, length 1, OP_HASH256, a 32-byte push, OP_EQUAL: 35+      outputSize OutputScript32Hash `shouldBe` 44+      outputSize OutputWitnessPKHash `shouldBe` 31+      outputSize OutputWitnessScriptHash `shouldBe` 43+      outputSize OutputTaproot `shouldBe` 43+      -- a bare 1-of-1 multisig script, OP_1, a 33-byte key push, OP_1, OP_CHECKMULTISIG: 37 bytes after a 1-byte length+      outputSize (OutputScript 37) `shouldBe` 46+      -- an empty script is its 1-byte length+      outputSize (OutputScript 0) `shouldBe` 9+      -- a 1000-byte script after its 3-byte length+      outputSize (OutputScript 1000) `shouldBe` 1011++    it "gives the weight and virtual size of a transaction of each shape" $ do+      -- 8 + 1 + 148 + 1 + 34 = 192 bytes, no witness+      guessTxWeight [InputPKHash] [OutputPKHash] `shouldBe` 768+      guessTxSize [InputPKHash] [OutputPKHash] `shouldBe` 192+      -- 8 + 1 + 41 + 1 + 31 = 82 bytes without witness, 2 + 108 of witness: 438 units, 109.5 rounded up+      guessTxWeight [InputWitnessPKHash] [OutputWitnessPKHash] `shouldBe` 438+      guessTxSize [InputWitnessPKHash] [OutputWitnessPKHash] `shouldBe` 110+      -- 8 + 1 + 64 + 1 + 32 = 106 bytes, 110 of witness: 534 units+      guessTxSize [InputNestedWitnessPKHash] [OutputScriptHash] `shouldBe` 134+      -- 8 + 1 + 41 + 1 + 43 = 94 bytes, 2 + 67 of witness: 445 units+      guessTxSize [InputTaproot] [OutputTaproot] `shouldBe` 112+      -- 8 + 1 + 297 + 1 + 34 + 32 = 373 bytes, no witness+      guessTxSize [InputMulSig 2 3] [OutputPKHash, OutputScriptHash] `shouldBe` 373+      -- 8 + 1 + 41 + 1 + 43 = 94 bytes, 2 + 254 of witness: 632 units+      guessTxSize [InputWitnessMulSig 2 3] [OutputWitnessScriptHash] `shouldBe` 158+      -- 8 + 1 + 76 + 1 + 34 = 120 bytes, 256 of witness: 736 units+      guessTxSize [InputNestedWitnessMulSig 2 3] [OutputPKHash] `shouldBe` 184+      -- 8 + 1 + 148 + 41 + 1 + 34 = 233 bytes; the witness: 2, the empty stack of the legacy input 1, 108: 1043 units, 260.75 rounded up+      guessTxSize [InputPKHash, InputWitnessPKHash] [OutputPKHash] `shouldBe` 261++    it "counts the empty witness stack of each input without a witness when another has one" $ do+      -- 4 + 1 + 3 × 148 + 41 + 1 + 34 + 4 = 529 bytes; the witness: 2 + 1 + 1 + 1 + 108 = 113; 3 × 529 + 642 = 2229 units, 557.25 rounded up+      guessTxWeight [InputPKHash, InputPKHash, InputPKHash, InputWitnessPKHash] [OutputPKHash] `shouldBe` 2229+      guessTxSize [InputPKHash, InputPKHash, InputPKHash, InputWitnessPKHash] [OutputPKHash] `shouldBe` 558+      -- 8 + 1 + 114 + 41 + 1 + 44 = 209 bytes; the witness: 2 + 1 + 67 = 70: 906 units, 226.5 rounded up+      guessTxWeight [InputPK, InputTaproot] [OutputScript32Hash] `shouldBe` 906+      guessTxSize [InputPK, InputTaproot] [OutputScript32Hash] `shouldBe` 227+      -- without a witness, no stack is counted: 4 × (8 + 1 + 296 + 1 + 34)+      guessTxWeight [InputPKHash, InputPKHash] [OutputPKHash] `shouldBe` 1360++    it "charges the fee rate per virtual byte" $ do+      guessTxFee 10 [InputPKHash] [OutputPKHash] `shouldBe` 1920+      guessTxFee 10 [InputWitnessPKHash] [OutputWitnessPKHash] `shouldBe` 1100+      guessTxFee 0 [InputPKHash] [OutputPKHash] `shouldBe` 0++    it "is at least the virtual size of a signed P2WPKH spend, and of a Taproot key path spend" $ do+      (k0, _) <- generate (arbitraryKeyPair ctx)+      -- SegWit keys are compressed+      let k = PrivateKey k0.key True+          p = derivePublicKey ctx k+          h = fromJust (addressHash160 (pubKeyAddr ctx p))+          op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0+          witnessTx = buildTx ctx [op] [(PayWitnessPKHash h, 90000)]+          taprootOutput = taprootScriptOutput ctx (TaprootOutput p.point Nothing)+          taprootTx = buildTx ctx [op] [(taprootOutput, 90000)]+      signedWitness <- either fail return (signTx btc ctx witnessTx [SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing] [k.key])+      signedTaproot <- either fail return (signTx btc ctx taprootTx [SigInput taprootOutput 100000 op sigHashDefault Nothing] [k.key])+      -- The witness of the taproot spend is 64 bytes, one less than counted+      txVirtualSize signedWitness `shouldSatisfy` (\v -> v <= 110 && v >= 108)+      txVirtualSize signedTaproot `shouldSatisfy` (\v -> v <= 112 && v >= 111)++{- Coin selection -}++coinSelectionSpec :: Spec+coinSelectionSpec =+  describe "Coin selection" $ do+    -- One P2PKH input and two P2PKH outputs: 8 + 1 + 148 + 1 + 68 = 226+    -- virtual bytes, 2260 satoshi at 10 satoshi per virtual byte. The first+    -- coin covers 60000 + 2260, with 37740 of change.+    it "selects the first coins that cover the target and the fee of the transaction they make" $ do+      let coins = [TestCoin 100000 InputPKHash, TestCoin 50000 InputPKHash]+      chooseCoins 60000 10 [OutputPKHash, OutputPKHash] False coins `shouldBe` Right ([TestCoin 100000 InputPKHash], 37740)+      chooseCoins 60000 10 [OutputPKHash, OutputPKHash] True coins `shouldBe` Right ([TestCoin 100000 InputPKHash], 37740)++    -- Two P2WPKH inputs and one P2WPKH output: 8 + 1 + 82 + 1 + 31 = 123+    -- bytes without witness, 2 + 216 with it: 710 units, 178 virtual+    -- bytes, 1780 satoshi. Neither coin alone covers 60000 and the fee of+    -- one input (8 + 1 + 41 + 1 + 31 = 82, 2 + 108: 438 units, 110 bytes,+    -- 1100 satoshi), both together do, with 90000 - 60000 - 1780 of change.+    it "adds coins until they cover the target and the fee, which grows with them" $ do+      let coins = [TestCoin 50000 InputWitnessPKHash, TestCoin 40000 InputWitnessPKHash]+      chooseCoins 60000 10 [OutputWitnessPKHash] False coins `shouldBe` Right ([TestCoin 40000 InputWitnessPKHash, TestCoin 50000 InputWitnessPKHash], 28220)++    it "looks for a smaller solution after the first when asked to continue" $ do+      let coins = [TestCoin 30000 InputPKHash, TestCoin 40000 InputPKHash, TestCoin 65000 InputPKHash]+      -- 30000 + 40000 covers 60000 + 10 × (8 + 1 + 296 + 1 + 34) = 60000 + 3400; then 65000 alone covers 60000 + 1920, and is smaller+      chooseCoins 60000 10 [OutputPKHash] False coins `shouldBe` Right ([TestCoin 40000 InputPKHash, TestCoin 30000 InputPKHash], 6600)+      chooseCoins 60000 10 [OutputPKHash] True coins `shouldBe` Right ([TestCoin 65000 InputPKHash], 3080)++    it "refuses a target of zero and reports when the coins do not suffice" $ do+      chooseCoins 0 10 [OutputPKHash] False [TestCoin 100000 InputPKHash] `shouldBe` Left "chooseCoins: Target must be > 0"+      chooseCoins 100000 10 [OutputPKHash] False [TestCoin 100000 InputPKHash] `shouldBe` Left "chooseCoins: No solution found"+      chooseCoins 1 10 [OutputPKHash] False ([] :: [TestCoin]) `shouldBe` Left "chooseCoins: No solution found"++{- Verification -}++verificationSpec :: Ctx -> Spec+verificationSpec ctx =+  describe "Verification" $ do+    let coinOf = OutPoint (fromJust (hexToTxHash genesisTxId))+    it "accepts a correct P2PKH spend, and refuses a witness on its input, a wrong key, a wrong amount and a missing coin" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      (_, other) <- generate (arbitraryKeyPair ctx)+      let h = fromJust (addressHash160 (pubKeyAddr ctx p))+          otherHash = fromJust (addressHash160 (pubKeyAddr ctx other))+          op = coinOf 0+          tx = buildTx ctx [op] [(PayPKHash h, 90000)]+      signed <- either fail return (signTx btc ctx tx [SigInput (PayPKHash h) 100000 op sigHashAll Nothing] [k.key])+      verifyStdTx btc ctx signed [(PayPKHash h, 100000, op)] `shouldBe` True+      verifyStdTx btc ctx signed {witness = [["junk"]]} [(PayPKHash h, 100000, op)] `shouldBe` False+      verifyStdTx btc ctx signed [(PayPKHash otherHash, 100000, op)] `shouldBe` False+      -- The legacy digest does not commit to the amount, but the outputs may not spend more than the coin+      verifyStdTx btc ctx signed [(PayPKHash h, 89999, op)] `shouldBe` False+      verifyStdTx btc ctx signed [(PayPKHash h, 100000, coinOf 1)] `shouldBe` False+      verifyStdTx btc ctx signed [] `shouldBe` False+      verifyStdInput btc ctx signed [(PayPKHash h, 100000, op)] 0 `shouldBe` True+      verifyStdInput btc ctx signed [(PayPKHash h, 100000, op)] 1 `shouldBe` False+      verifyStdInput btc ctx signed [(PayPKHash h, 100000, op)] (-1) `shouldBe` False++    it "refuses a transaction without inputs, and the outputs of a transaction spending more than its coins" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let h = fromJust (addressHash160 (pubKeyAddr ctx p))+          ops = [coinOf 0, coinOf 1]+          tx = buildTx ctx ops [(PayPKHash h, 150000)]+          sigis = [SigInput (PayPKHash h) 100000 op sigHashAll Nothing | op <- ops]+          coins = [(PayPKHash h, 100000, op) | op <- ops]+      signed <- either fail return (signTx btc ctx tx sigis [k.key])+      verifyStdTx btc ctx signed coins `shouldBe` True+      verifyStdTx btc ctx signed (take 1 coins) `shouldBe` False+      verifyStdTx btc ctx (buildTx ctx [] [(PayPKHash h, 1)]) [] `shouldBe` False+      -- The same coins hold less than the outputs spend+      verifyStdTx btc ctx signed [(PayPKHash h, 70000, op) | op <- ops] `shouldBe` False++    it "signs SIGHASH_SINGLE on a legacy input with an output at its index and on a SegWit input, but refuses it on a legacy input without one" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let h = fromJust (addressHash160 (pubKeyAddr ctx p))+          ops = [coinOf 0, coinOf 1]+          -- one output, so input 1 has none at its index+          tx = buildTx ctx ops [(PayPKHash h, 150000)]+          legacy op = SigInput (PayPKHash h) 100000 op sigHashSingle Nothing+          segwit op = SigInput (PayWitnessPKHash h) 100000 op sigHashSingle Nothing+      signedFirst <- either fail return (signTx btc ctx tx [legacy (coinOf 0)] [k.key])+      verifyStdInput btc ctx signedFirst [(PayPKHash h, 100000, coinOf 0)] 0 `shouldBe` True+      signTx btc ctx tx [legacy (coinOf 1)] [k.key] `shouldBe` Left "makeSignature: SIGHASH_SINGLE without an output at the input index"+      -- The BIP 143 digest commits to the coin even without an output+      signedWitness <- either fail return (signTx btc ctx tx [segwit (coinOf 1)] [k.key])+      verifyStdInput btc ctx signedWitness [(PayWitnessPKHash h, 100000, coinOf 1)] 1 `shouldBe` True++    -- A node accepts the signature over the constant digest 1 that a+    -- legacy SIGHASH_SINGLE input without an output has, so the verifier+    -- must too, although the signer refuses to make one+    it "accepts a legacy SIGHASH_SINGLE signature over the constant digest 1, as a node does" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let h = fromJust (addressHash160 (pubKeyAddr ctx p))+          ops = [coinOf 0, coinOf 1]+          tx = buildTx ctx ops [(PayPKHash h, 150000)]+          one = "0100000000000000000000000000000000000000000000000000000000000000" :: Hash256+          overOne = marshal (btc, ctx) (RegularInput (SpendPKHash (TxSignature (signHash ctx k.key one) sigHashSingle) p))+          spent = Tx tx.version [TxIn (coinOf 0) "" 0xffffffff, TxIn (coinOf 1) overOne 0xffffffff] tx.outputs [] tx.locktime+          spentFirst = Tx tx.version [TxIn (coinOf 0) overOne 0xffffffff, TxIn (coinOf 1) "" 0xffffffff] tx.outputs [] tx.locktime+      verifyStdInput btc ctx spent [(PayPKHash h, 100000, coinOf 1)] 1 `shouldBe` True+      -- The same signature on input 0, which has an output, does not verify+      verifyStdInput btc ctx spentFirst [(PayPKHash h, 100000, coinOf 0)] 0 `shouldBe` False++    it "refuses a SegWit input with a non-empty input script and a wrong amount" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let h = fromJust (addressHash160 (pubKeyAddr ctx p))+          op = coinOf 0+          tx = buildTx ctx [op] [(PayWitnessPKHash h, 90000)]+      signed <- either fail return (signTx btc ctx tx [SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing] [k.key])+      verifyStdTx btc ctx signed [(PayWitnessPKHash h, 100000, op)] `shouldBe` True+      -- The BIP 143 digest commits to the amount+      verifyStdTx btc ctx signed [(PayWitnessPKHash h, 100001, op)] `shouldBe` False+      let pushed = Tx signed.version [TxIn op "\x51" i.sequence | i <- signed.inputs] signed.outputs signed.witness signed.locktime+      verifyStdTx btc ctx pushed [(PayWitnessPKHash h, 100000, op)] `shouldBe` False++{- Merging -}++mergeSpec :: Ctx -> Spec+mergeSpec ctx =+  describe "Merging partially signed transactions" $ do+    it "leaves an input that no transaction signed unsigned, and merges the others" $ do+      (k1, p1) <- generate (arbitraryKeyPair ctx)+      (k2, p2) <- generate (arbitraryKeyPair ctx)+      let ms = PayMulSig [p1, p2] 2+          so = PayScriptHash (fromJust (addressHash160 (payToScriptAddress ctx ms)))+          ops = [OutPoint (fromJust (hexToTxHash genesisTxId)) 0, OutPoint (fromJust (hexToTxHash genesisTxId)) 1]+          tx = buildTx ctx ops [(PayPKHash (fromJust (addressHash160 (pubKeyAddr ctx p1))), 90000)]+          coins = [(so, 100000, op) | op <- ops]+          sigi = SigInput so 100000 (head ops) sigHashAll (Just ms)+      a <- either fail return (signTx btc ctx tx [sigi] [k1.key])+      b <- either fail return (signTx btc ctx tx [sigi] [k2.key])+      merged <- either fail return (mergeTxs btc ctx [a, b] coins)+      (merged.inputs !! 1).script `shouldBe` ""+      verifyStdInput btc ctx merged coins 0 `shouldBe` True+      verifyStdTx btc ctx merged coins `shouldBe` False+      -- The second input signed by both completes the transaction+      let sigi2 = SigInput so 100000 (ops !! 1) sigHashAll (Just ms)+      c <- either fail return (signTx btc ctx tx [sigi2] [k1.key])+      d <- either fail return (signTx btc ctx tx [sigi2] [k2.key])+      complete <- either fail return (mergeTxs btc ctx [a, b, c, d] coins)+      verifyStdTx btc ctx complete coins `shouldBe` True++    it "countMulSig counts the signatures that verify, in key order" $ do+      (k1, p1) <- generate (arbitraryKeyPair ctx)+      (k2, p2) <- generate (arbitraryKeyPair ctx)+      (k3, p3) <- generate (arbitraryKeyPair ctx)+      let ms = PayMulSig [p1, p2, p3] 2+          msScript = either error id (encodeOutput ctx ms)+          op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0+          tx = buildTx ctx [op] [(PayPKHash (fromJust (addressHash160 (pubKeyAddr ctx p1))), 90000)]+          sigOf k = TxSignature (signHash ctx k.key (either error id (txSigHash btc tx msScript 100000 0 sigHashAll))) sigHashAll+          count = countMulSig btc ctx tx msScript 100000 0 (map (.point) [p1, p2, p3])+      count [sigOf k1, sigOf k3] `shouldBe` 2+      count [sigOf k1, sigOf k2, sigOf k3] `shouldBe` 3+      count [sigOf k1, TxSignatureEmpty, sigOf k3] `shouldBe` 2+      -- A signature out of key order is not counted, nor a signature of another digest+      count [sigOf k3, sigOf k1] `shouldBe` 1+      count [TxSignature (signHash ctx k1.key (sha256 ("other" :: ByteString))) sigHashAll] `shouldBe` 0+      count [] `shouldBe` 0++    it "findSigInput pairs the signing parameters with the index of their input" $ do+      let ops = [OutPoint (fromJust (hexToTxHash genesisTxId)) i | i <- [0 .. 2]]+          tx = buildTx ctx ops []+          sigi op = SigInput (PayPKHash "0000000000000000000000000000000000000000") 1 op sigHashAll Nothing+      map snd (findSigInput [sigi (ops !! 2), sigi (ops !! 0)] tx.inputs) `shouldBe` [0, 2]+      findSigInput [sigi (OutPoint (fromJust (hexToTxHash genesisTxId)) 9)] tx.inputs `shouldBe` []++    it "buildInput refuses an index out of range, a Taproot output and a mismatched redeem script" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0+          tx = buildTx ctx [op] []+          sig = TxSignature (signHash ctx k.key (sha256 ("digest" :: ByteString))) sigHashAll+          h = fromJust (addressHash160 (pubKeyAddr ctx p))+      buildInput btc ctx tx 1 (PayPKHash h) 1 Nothing sig p `shouldBe` Left "buildInput: Invalid input index"+      buildInput btc ctx tx 0 (taprootScriptOutput ctx (TaprootOutput p.point Nothing)) 1 Nothing sig p `shouldBe` Left "buildInput: a Taproot input has no input script"+      buildInput btc ctx tx 0 (PayPKHash h) 1 (Just (PayPK p)) sig p `shouldBe` Left "buildInput: Invalid output/redeem script combination"+      buildInput btc ctx tx 0 (PayPKHash h) 1 Nothing sig p `shouldBe` Right (RegularInput (SpendPKHash sig p))++    it "refuses an empty list and transactions that differ" $ do+      mergeTxs btc ctx [] [] `shouldBe` Left "Transaction list is empty"+      let op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0+          one = buildTx ctx [op] []+          other = buildTx ctx [op] [(PayPK (PublicKey (derivePubKey ctx (fromJust (secKey (B.replicate 32 1)))) True), 1)]+      mergeTxs btc ctx [one] [] `shouldBe` Right one+      mergeTxs btc ctx [one, other] [] `shouldBe` Left "Transactions do not match"++{- Taproot -}++taprootSigningSpec :: Ctx -> Spec+taprootSigningSpec ctx =+  describe "Taproot key path signing" $ do+    let op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0+    it "signs with the internal key of an output without script tree, as BIP 86 spends, and with the output key itself" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let so = taprootScriptOutput ctx (TaprootOutput p.point Nothing)+          tx = buildTx ctx [op] [(so, 90000)]+          sigi = SigInput so 100000 op sigHashDefault Nothing+          tweaked = fromJust (taprootOutputSecKey ctx k.key Nothing)+      byInternal <- either fail return (signTx btc ctx tx [sigi] [k.key])+      byOutput <- either fail return (signTx btc ctx tx [sigi] [tweaked])+      byInternal `shouldBe` byOutput+      map length byInternal.witness `shouldBe` [1]+      map (B.length . head) byInternal.witness `shouldBe` [64]+      verifyStdTx btc ctx byInternal [(so, 100000, op)] `shouldBe` True+      -- A signature with a hash type is 65 bytes and verifies as well+      withType <- either fail return (signTx btc ctx tx [sigi {sighash = setAnyoneCanPay sigHashAll}] [k.key])+      map (B.length . head) withType.witness `shouldBe` [65]+      verifyStdTx btc ctx withType [(so, 100000, op)] `shouldBe` True+      -- Another key does not sign+      (other, _) <- generate (arbitraryKeyPair ctx)+      fmap (.witness) (signTx btc ctx tx [sigi] [other.key]) `shouldBe` Right []++    it "needs the output every input spends, and refuses signInput" $ do+      (k, p) <- generate (arbitraryKeyPair ctx)+      let so = taprootScriptOutput ctx (TaprootOutput p.point Nothing)+          other = OutPoint (fromJust (hexToTxHash genesisTxId)) 1+          tx = buildTx ctx [op, other] [(so, 90000)]+          sigi = SigInput so 100000 op sigHashDefault Nothing+      signTx btc ctx tx [sigi] [k.key] `shouldBe` Left "signTx: a Taproot signature needs the output every input spends"+      signInput btc ctx tx 0 sigi k `shouldBe` Left "signInput: a Taproot input is signed with signTaprootInput"+      signTaprootInput ctx tx [TxOut 100000 (marshal ctx so)] 0 sigHashDefault k.key `shouldBe` Left "taprootSigHash: one spent output per input is required"+      signTaprootInput ctx tx [TxOut 100000 (marshal ctx so), TxOut 1 ""] 0 (setForkIdFlag sigHashAll) k.key `shouldBe` Left "taprootSigHash: invalid hash type 65"++{- SegWit helpers -}++segwitSpec :: Ctx -> Spec+segwitSpec ctx =+  describe "SegWit witness helpers" $ do+    (k, p) <- runIO (generate (arbitraryKeyPair ctx))+    let digest = sha256 ("message" :: ByteString)+        sig = TxSignature (signHash ctx k.key digest) sigHashAll+        sigBytes = encodeTxSig btc ctx sig+        keyBytes = marshal ctx p+        h = fromJust (addressHash160 (pubKeyAddr ctx p))+        multisig = PayMulSig [p] 1+        multisigScript = either error id (encodeOutput ctx multisig)+        wsh = toP2WSH multisigScript+    it "isSegwit is true for the version 0 programs only" $ do+      isSegwit (PayWitnessPKHash h) `shouldBe` True+      isSegwit wsh `shouldBe` True+      isSegwit (PayPKHash h) `shouldBe` False+      isSegwit (PayWitness 1 (B.replicate 32 0)) `shouldBe` False++    it "a simple input stacks its signatures, keys and the empty item of multisig" $ do+      simpleInputStack btc ctx (SpendPK sig) `shouldBe` [sigBytes]+      simpleInputStack btc ctx (SpendPKHash sig p) `shouldBe` [sigBytes, keyBytes]+      simpleInputStack btc ctx (SpendMulSig [sig, TxSignatureEmpty]) `shouldBe` ["", sigBytes, ""]++    it "a witness program encodes to its stack, with the script last for P2WSH" $ do+      toWitnessStack btc ctx (P2WPKH (WitnessProgramPKH sig p)) `shouldBe` [sigBytes, keyBytes]+      toWitnessStack btc ctx (P2WSH (WitnessProgramSH ["", sigBytes] multisigScript)) `shouldBe` ["", sigBytes, runPutS (serialize multisigScript)]+      toWitnessStack btc ctx EmptyWitnessProgram `shouldBe` []++    it "a witness is read against the output it spends" $ do+      viewWitnessProgram btc ctx (PayWitnessPKHash h) [sigBytes, keyBytes] `shouldBe` Right (P2WPKH (WitnessProgramPKH sig p))+      viewWitnessProgram btc ctx (PayWitnessPKHash h) [sigBytes] `shouldBe` Left "viewWitnessProgram: Invalid witness program"+      viewWitnessProgram btc ctx wsh ["", sigBytes, runPutS (serialize multisigScript)] `shouldBe` Right (P2WSH (WitnessProgramSH ["", sigBytes] multisigScript))+      viewWitnessProgram btc ctx (PayPKHash h) [] `shouldBe` Right EmptyWitnessProgram+      viewWitnessProgram btc ctx (PayPKHash h) [sigBytes] `shouldBe` Left "viewWitnessProgram: Invalid witness program"++    it "a witness program decodes to the input it stands for" $ do+      decodeWitnessInput btc ctx (P2WPKH (WitnessProgramPKH sig p)) `shouldBe` Right (Nothing, SpendPKHash sig p)+      decodeWitnessInput btc ctx (P2WSH (WitnessProgramSH ["", sigBytes] multisigScript)) `shouldBe` Right (Just multisig, SpendMulSig [sig])+      decodeWitnessInput btc ctx (P2WSH (WitnessProgramSH [sigBytes] multisigScript)) `shouldBe` Left "decodeWitnessInput: Non-standard script output"+      decodeWitnessInput btc ctx EmptyWitnessProgram `shouldBe` Left "decodeWitnessInput: Empty witness program"++    it "a witness program is computed from a standard input, native or nested" $ do+      calcWitnessProgram btc ctx (PayWitnessPKHash h) (RegularInput (SpendPKHash sig p)) `shouldBe` Right (P2WPKH (WitnessProgramPKH sig p))+      calcWitnessProgram btc ctx (toP2SH (either error id (encodeOutput ctx (PayWitnessPKHash h)))) (RegularInput (SpendPKHash sig p)) `shouldBe` Right (P2WPKH (WitnessProgramPKH sig p))+      calcWitnessProgram btc ctx wsh (ScriptHashInput (SpendMulSig [sig]) multisig) `shouldBe` Right (P2WSH (WitnessProgramSH ["", sigBytes] multisigScript))+      calcWitnessProgram btc ctx (PayPKHash h) (RegularInput (SpendPKHash sig p)) `shouldBe` Left "calcWitnessProgram: Invalid segwit SigInput"++{- Genesis -}++genesisSpec :: Spec+genesisSpec =+  describe "Genesis coinbases" $ do+    it "genesisCoinbase builds a version 1 transaction with one null input and one output of 50 BTC" $ do+      let tx = genesisCoinbase "in" "out"+      tx `shouldBe` Tx 1 [TxIn nullOutPoint "in" 0xffffffff] [TxOut 5000000000 "out"] [] 0+      nullOutPoint `shouldBe` OutPoint "0000000000000000000000000000000000000000000000000000000000000000" 0xffffffff+    it "the mainnet coinbase has the id of the genesis block's only transaction" $+      txHashToHex (txHash mainnetGenesisTx) `shouldBe` "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"+    it "the testnet4 coinbase has its own id" $+      txHashToHex (txHash testnet4GenesisTx) `shouldBe` "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e"
test/Haskoin/UtilSpec.hs view
@@ -1,26 +1,40 @@+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-} +-- | The helpers of "Haskoin.Util": bytes and hex, integers, lists and+-- templates, bit conversion, JSON options, the serialization helpers and+-- the marshalling classes. module Haskoin.UtilSpec (spec) where +import Control.Exception (ErrorCall, evaluate)+import Control.Monad.Except (ExceptT, runExceptT) import Data.Aeson import Data.Aeson.Encoding (encodingToLazyByteString)-import Data.Aeson.Types (Parser, parse) import Data.ByteString (ByteString) import Data.ByteString qualified as B+import Data.ByteString.Builder (toLazyByteString)+import Data.ByteString.Lazy qualified as BL+import Data.Bytes.Get+import Data.Bytes.Put import Data.Default (def)-import Data.Either (fromLeft, fromRight, isLeft, isRight)+import Data.Either (isLeft) import Data.Foldable (toList)+import Data.Int (Int32, Int64)+import Data.IntMap qualified as IntMap import Data.List (permutations)-import Data.Map.Strict (singleton) import Data.Maybe import Data.Sequence qualified as Seq-import Data.Serialize as S+import Data.Serialize qualified as S+import Data.Word (Word8)+import GHC.Generics (Generic) import Haskoin.Crypto import Haskoin.Util import Haskoin.Util.Arbitrary import Test.Hspec import Test.Hspec.QuickCheck-import Test.QuickCheck (forAll)+import Test.QuickCheck  identityTests :: IdentityTests identityTests =@@ -30,19 +44,216 @@         ]     } +-- A record and a sum type for the JSON options+data Point = Point {ptX :: Int, ptY :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON Point where+  toJSON = genericToJSON (dropFieldLabel 2)++instance FromJSON Point where+  parseJSON = genericParseJSON (dropFieldLabel 2)++data Shape = SCircle {scRadius :: Int} | SSquare {ssSide :: Int}+  deriving (Eq, Show, Generic)++instance ToJSON Shape where+  toJSON = genericToJSON (dropSumLabels 1 2 "type")++instance FromJSON Shape where+  parseJSON = genericParseJSON (dropSumLabels 1 2 "type")+ spec :: Spec-spec =+spec = prepareContext $ \ctx ->   describe "utility functions" $ do     testIdentity identityTests-    prop "bsToInteger . integerToBS" getPutInteger-    prop "decodeHex . encodeHex" $ forAll arbitraryBS fromToHex-    prop "compare updateIndex with Data.Sequence" testUpdateIndex-    prop "matchTemplate" testMatchTemplate-    prop "testing matchTemplate with two lists" testMatchTemplateLen-    prop "test eitherToMaybe" testEitherToMaybe-    prop "test maybeToEither" testMaybeToEither -{- Various utilities -}+    describe "Integers and bytes" $ do+      it "integerToBS writes big-endian bytes without leading zeros, and zero as one byte" $ do+        integerToBS 0 `shouldBe` "\x00"+        integerToBS 1 `shouldBe` "\x01"+        integerToBS 256 `shouldBe` "\x01\x00"+        integerToBS 0xdeadbeef `shouldBe` "\xde\xad\xbe\xef"+      it "integerToBS refuses a negative number" $+        evaluate (integerToBS (-1)) `shouldThrow` anyErrorCall+      it "bsToInteger reads big-endian bytes, and nothing as zero" $ do+        bsToInteger "\x01\x00" `shouldBe` 256+        bsToInteger "\x00\x00\x01" `shouldBe` 1+        bsToInteger "" `shouldBe` 0+      prop "bsToInteger . integerToBS" getPutInteger++    describe "Hex" $ do+      it "encodes lower case and decodes either case" $ do+        encodeHex "\xab\xcd" `shouldBe` "abcd"+        encodeHexLazy "\xab\xcd" `shouldBe` "abcd"+        decodeHex "abcd" `shouldBe` Just "\xab\xcd"+        decodeHex "ABCD" `shouldBe` Just "\xab\xcd"+        decodeHexLazy "ABCD" `shouldBe` Just "\xab\xcd"+        decodeHex "" `shouldBe` Just ""+      it "refuses an odd length, a character that is not hex, and spaces" $ do+        decodeHex "abc" `shouldBe` Nothing+        decodeHex "0g" `shouldBe` Nothing+        decodeHex " 00" `shouldBe` Nothing+        decodeHex "00 " `shouldBe` Nothing+        decodeHexLazy "abc" `shouldBe` Nothing+      it "hexEncoding is a JSON string of hex, and hexBuilder the hex alone" $ do+        encodingToLazyByteString (hexEncoding "\xab\xcd") `shouldBe` "\"abcd\""+        toLazyByteString (hexBuilder "\xab\xcd") `shouldBe` "abcd"+      prop "decodeHex . encodeHex" $ forAll arbitraryBS fromToHex+      prop "decodeHexLazy . encodeHexLazy" $ forAll arbitraryBS $ \bs -> decodeHexLazy (encodeHexLazy (BL.fromStrict bs)) == Just (BL.fromStrict bs)++    describe "getBits" $ do+      it "takes that many bits from the start, padded with zero bits" $ do+        getBits 4 "\xff\x00" `shouldBe` "\xf0"+        getBits 8 "\xff\x00" `shouldBe` "\xff"+        getBits 12 "\xff\xff" `shouldBe` "\xff\xf0"+        getBits 0 "\xff" `shouldBe` ""+      it "raises an error when the bytes do not hold that many bits" $ do+        evaluate (getBits 12 "\xff") `shouldThrow` anyErrorCall+        evaluate (getBits 4 "") `shouldThrow` anyErrorCall+        evaluate (getBits (-1) "\xff") `shouldThrow` anyErrorCall++    describe "Maybe and Either" $ do+      prop "eitherToMaybe" testEitherToMaybe+      prop "maybeToEither" testMaybeToEither+      it "liftMaybe and liftEither lift into ExceptT" $ do+        runExceptT (liftMaybe "none" (Just (1 :: Int))) `shouldReturn` Right 1+        runExceptT (liftMaybe "none" (Nothing :: Maybe Int)) `shouldReturn` Left "none"+        runExceptT (liftEither (Right (2 :: Int)) :: ExceptT' Int) `shouldReturn` Right 2+        runExceptT (liftEither (Left "bad") :: ExceptT' Int) `shouldReturn` Left "bad"++    describe "updateIndex" $ do+      it "changes the element at the index, and nothing when the index is out of range" $ do+        updateIndex 1 [1, 2, 3 :: Int] (* 10) `shouldBe` [1, 20, 3]+        updateIndex 0 [1, 2, 3 :: Int] (* 10) `shouldBe` [10, 2, 3]+        updateIndex 3 [1, 2, 3 :: Int] (* 10) `shouldBe` [1, 2, 3]+        updateIndex (-1) [1, 2, 3 :: Int] (* 10) `shouldBe` [1, 2, 3]+      prop "compare updateIndex with Data.Sequence" testUpdateIndex++    describe "matchTemplate" $ do+      it "gives for each element of the template the first matching input, each input once" $ do+        matchTemplate [1, 2, 2 :: Int] [2, 2, 3] (==) `shouldBe` [Just 2, Just 2, Nothing]+        matchTemplate [2 :: Int] [2, 2] (==) `shouldBe` [Just 2, Nothing]+        matchTemplate [] [1, 2 :: Int] (==) `shouldBe` [Nothing, Nothing]+        matchTemplate [1 :: Int] [] (==) `shouldBe` []+        matchTemplate ["a", "bb" :: String] [2, 1 :: Int] (\s n -> length s == n) `shouldBe` [Just "bb", Just "a"]+      prop "matchTemplate" testMatchTemplate+      prop "testing matchTemplate with two lists" testMatchTemplateLen++    describe "Triples" $+      it "fst3, snd3 and lst3 take the parts of a triple" $ do+        fst3 (1 :: Int, "b", 'c') `shouldBe` 1+        snd3 (1 :: Int, "b", 'c') `shouldBe` "b"+        lst3 (1 :: Int, "b", 'c') `shouldBe` 'c'++    describe "JSON options" $ do+      it "dropFieldLabel drops the prefix of a field and lowers its case" $ do+        encode (Point 1 2) `shouldBe` "{\"x\":1,\"y\":2}"+        decode "{\"x\":1,\"y\":2}" `shouldBe` Just (Point 1 2)+      it "dropSumLabels also drops the prefix of a constructor, which goes in the tag field" $ do+        encode (SCircle 3) `shouldBe` "{\"radius\":3,\"type\":\"circle\"}"+        encode (SSquare 4) `shouldBe` "{\"side\":4,\"type\":\"square\"}"+        decode "{\"type\":\"square\",\"side\":4}" `shouldBe` Just (SSquare 4)++    describe "convertBits" $ do+      -- 0xff is 11111111: the group 11111 and 111 padded to 11100+      it "regroups bits, padding the last group with zeros when asked" $ do+        convertBits True 8 5 [0xff] `shouldBe` Just [31, 28]+        convertBits True 5 8 [1] `shouldBe` Just [8]+        convertBits True 8 5 [] `shouldBe` Just []+      it "without padding, refuses leftover bits that are not zero or that could hold a whole digit" $ do+        convertBits False 5 8 [31, 28] `shouldBe` Just [255]+        convertBits False 5 8 [31, 31] `shouldBe` Nothing+        convertBits False 5 8 [1, 0] `shouldBe` Just [8]+        convertBits False 5 8 [1] `shouldBe` Nothing+        convertBits False 5 8 [] `shouldBe` Just []+      -- The data part of the BIP 173 address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4, as 5-bit groups, is the key hash of its example+      it "converts the 5-bit groups of the BIP 173 example to its 20-byte program" $+        fmap (B.pack . map fromIntegral) (convertBits False 5 8 [14, 20, 15, 7, 13, 26, 0, 25, 18, 6, 11, 13, 8, 21, 4, 20, 3, 17, 2, 29, 3, 12, 29, 3, 4, 15, 24, 20, 6, 14, 30, 22])+          `shouldBe` Just "\x75\x1e\x76\xe8\x19\x91\x96\xd4\x54\x94\x1c\x45\xd1\xb3\xa3\x23\xf1\x43\x3b\xd6"+      prop "converting to 5 bits with padding and back gives the bytes" $+        forAll arbitraryBS $ \bs ->+          let groups = fromJust (convertBits True 8 5 (map fromIntegral (B.unpack bs)))+           in fmap (map fromIntegral) (convertBits False 5 8 groups) === Just (B.unpack bs)++    describe "Serialization helpers" $ do+      it "putList and getList write a 64-bit count then the elements" $ do+        runPutS (putList putWord8 [1, 2]) `shouldBe` "\x00\x00\x00\x00\x00\x00\x00\x02\x01\x02"+        runGetS (getList getWord8) "\x00\x00\x00\x00\x00\x00\x00\x02\x01\x02" `shouldBe` Right [1, 2]+        runGetS (getList getWord8) "\x00\x00\x00\x00\x00\x00\x00\x00" `shouldBe` Right ([] :: [Word8])+      it "getList refuses a count that does not fit an Int instead of reading an empty list" $+        runGetS (getList getWord8) "\xff\xff\xff\xff\xff\xff\xff\xff\x41" `shouldSatisfy` isLeft+      it "putMaybe and getMaybe write a tag byte" $ do+        runPutS (putMaybe putWord8 Nothing) `shouldBe` "\x00"+        runPutS (putMaybe putWord8 (Just 7)) `shouldBe` "\x01\x07"+        runGetS (getMaybe getWord8) "\x01\x07" `shouldBe` Right (Just 7)+        runGetS (getMaybe getWord8) "\x00" `shouldBe` Right (Nothing :: Maybe Word8)+        runGetS (getMaybe getWord8) "\x02" `shouldSatisfy` isLeft+      it "putLengthBytes and getLengthBytes write a 64-bit length then the bytes" $ do+        runPutS (putLengthBytes "abc") `shouldBe` "\x00\x00\x00\x00\x00\x00\x00\x03\&abc"+        runGetS getLengthBytes "\x00\x00\x00\x00\x00\x00\x00\x03\&abc" `shouldBe` Right "abc"+        runGetS getLengthBytes "\xff\xff\xff\xff\xff\xff\xff\xff\x41" `shouldSatisfy` isLeft+        runGetS getLengthBytes "\x00\x00\x00\x00\x00\x00\x00\x03\&ab" `shouldSatisfy` isLeft+      it "putInteger writes a 32-bit number after a zero byte, and a wider one with its sign, its length and its bytes" $ do+        runPutS (putInteger 0) `shouldBe` "\x00\x00\x00\x00\x00"+        runPutS (putInteger (-1)) `shouldBe` "\x00\xff\xff\xff\xff"+        runPutS (putInteger 2147483647) `shouldBe` "\x00\x7f\xff\xff\xff"+        -- 2^31: sign 1, four bytes, little-endian+        runPutS (putInteger 2147483648) `shouldBe` "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x80"+        -- -(2^31 + 1): sign 0xff+        runPutS (putInteger (-2147483649)) `shouldBe` "\x01\xff\x00\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x80"+      it "getInteger reads both forms back" $ do+        runGetS getInteger "\x00\xff\xff\xff\xff" `shouldBe` Right (-1)+        runGetS getInteger "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x80" `shouldBe` Right 2147483648+        runGetS getInteger "\x01\xff\x00\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x80" `shouldBe` Right (-2147483649)+      prop "getInteger . putInteger" $ \i -> runGetS getInteger (runPutS (putInteger i)) === Right (i :: Integer)+      prop "getInteger . putInteger for wide numbers" $ \(i :: Integer) (k :: Int) ->+        let n = i * 2 ^ (abs k `mod` 256) in runGetS getInteger (runPutS (putInteger n)) === Right n+      it "putInt32be and putInt64be write two's complement, big-endian" $ do+        runPutS (putInt32be (-1)) `shouldBe` "\xff\xff\xff\xff"+        runPutS (putInt32be minBound) `shouldBe` "\x80\x00\x00\x00"+        runPutS (putInt32be 1) `shouldBe` "\x00\x00\x00\x01"+        runPutS (putInt64be (-2)) `shouldBe` "\xff\xff\xff\xff\xff\xff\xff\xfe"+        runPutS (putInt64be minBound) `shouldBe` "\x80\x00\x00\x00\x00\x00\x00\x00"+        runGetS getInt32be "\x80\x00\x00\x00" `shouldBe` Right (minBound :: Int32)+        runGetS getInt64be "\xff\xff\xff\xff\xff\xff\xff\xfe" `shouldBe` Right (-2 :: Int64)+      prop "getInt32be . putInt32be" $ \i -> runGetS getInt32be (runPutS (putInt32be i)) === Right (i :: Int32)+      prop "getInt64be . putInt64be" $ \i -> runGetS getInt64be (runPutS (putInt64be i)) === Right (i :: Int64)+      it "putIntMap and getIntMap write the pairs in key order after their count" $ do+        let m = IntMap.fromList [(2, 20), (1, 10 :: Word8)]+        runPutS (putIntMap (putWord8 . fromIntegral) putWord8 m) `shouldBe` "\x00\x00\x00\x00\x00\x00\x00\x02\x01\x0a\x02\x14"+        runGetS (getIntMap (fromIntegral <$> getWord8) getWord8) "\x00\x00\x00\x00\x00\x00\x00\x02\x01\x0a\x02\x14" `shouldBe` Right m+      it "putTwo and getTwo write a pair" $ do+        runPutS (putTwo putWord8 putWord16be (1, 2)) `shouldBe` "\x01\x00\x02"+        runGetS (getTwo getWord8 getWord16be) "\x01\x00\x02" `shouldBe` Right (1, 2)++    describe "Marshalling" $ do+      let key = fromJust (secKey (B.replicate 32 1))+          public = PublicKey (derivePubKey ctx key) True+      it "marshal and unmarshal round trip, strict and lazy" $ do+        unmarshal ctx (marshal ctx public) `shouldBe` Right public+        unmarshalLazy ctx (marshalLazy ctx public) `shouldBe` Right public+        (unmarshalLazy ctx "junk" :: Either String PublicKey) `shouldSatisfy` isLeft+      it "marshalJSON and unmarshalJSON round trip, and refuse what is not the value" $ do+        unmarshalJSON ctx (marshalJSON ctx public) `shouldBe` Just public+        (unmarshalJSON ctx "\"junk\"" :: Maybe PublicKey) `shouldBe` Nothing+        (unmarshalJSON ctx "{" :: Maybe PublicKey) `shouldBe` Nothing++    describe "Test helpers" $ do+      it "customCerealID tells whether a getter reads what a putter writes" $ do+        customCerealID S.getWord8 S.putWord8 7 `shouldBe` True+        customCerealID S.getWord16be S.putWord16le 7 `shouldBe` False+      it "readTestFile reads a JSON file of the data directory" $ do+        value <- readTestFile "sig_strict.json" :: IO Value+        value `shouldSatisfy` \v -> case v of Array _ -> True; _ -> False+        parsed <- readTestFileParser parseJSON "sig_strict.json" :: IO [Value]+        length parsed `shouldSatisfy` (> 0)+      it "readTestFile raises an error for a file that is not there" $+        (readTestFile "no-such-file.json" :: IO Value) `shouldThrow` anyException++type ExceptT' a = ExceptTString a++type ExceptTString a = ExceptT String IO a  getPutInteger :: Integer -> Bool getPutInteger i = bsToInteger (integerToBS $ abs i) == abs i