haskoin-core 1.5.1 → 1.6.0
raw patch · 64 files changed
+17537/−1532 lines, 64 filesdep +unicode-dataPVP ok
version bump matches the API change (PVP)
Dependencies added: unicode-data
API changes (from Hackage documentation)
- Haskoin.Crypto.Keys.Extended: [:/] :: forall t. AnyOrSoft t => !DerivPathI t -> !KeyIndex -> DerivPathI t
- Haskoin.Crypto.Keys.Extended: [:|] :: forall t. HardOrAny t => !DerivPathI t -> !KeyIndex -> DerivPathI t
- Haskoin.Transaction.Taproot: TaprootSignature :: !SchnorrSig -> !SigHash -> TaprootSignature
- Haskoin.Transaction.Taproot: [hash] :: TaprootSignature -> !SigHash
- Haskoin.Transaction.Taproot: [sig] :: TaprootSignature -> !SchnorrSig
+ Haskoin.Address: p2shP2wpkhAddr :: Hash160 -> Address
+ Haskoin.Block.Merkle: extractIndexedMatches :: Network -> FlagBits -> PartialMerkleTree -> Int -> Either String (MerkleRoot, [(Int, TxHash)])
+ Haskoin.Block.Merkle: maxMerkleFlagBits :: Integer -> Integer -> Integer
+ Haskoin.Block.Merkle: merkleBlockIndexedTxs :: Network -> MerkleBlock -> Either String [(Int, TxHash)]
+ Haskoin.Crypto.Keys.Extended: pattern (:/) :: () => AnyOrSoft t => DerivPathI t -> KeyIndex -> DerivPathI t
+ Haskoin.Crypto.Keys.Extended: tryDerivePath :: Ctx -> DerivPathI t -> XPrvKey -> Either String XPrvKey
+ Haskoin.Crypto.Keys.Extended: tryDerivePubPath :: Ctx -> SoftPath -> XPubKey -> Either String XPubKey
+ Haskoin.Crypto.Keys.Slip39: EncryptedMasterSecret :: !Int -> !Bool -> !Int -> !ByteString -> EncryptedMasterSecret
+ Haskoin.Crypto.Keys.Slip39: Scheme :: !Int -> ![(Int, Int)] -> Scheme
+ Haskoin.Crypto.Keys.Slip39: Share :: !Int -> !Bool -> !Int -> !Int -> !Int -> !Int -> !Int -> !Int -> !ByteString -> Share
+ Haskoin.Crypto.Keys.Slip39: SplitRandom :: ![ByteString] -> !ByteString -> SplitRandom
+ Haskoin.Crypto.Keys.Slip39: [ciphertext] :: EncryptedMasterSecret -> !ByteString
+ Haskoin.Crypto.Keys.Slip39: [digestRandom] :: SplitRandom -> !ByteString
+ Haskoin.Crypto.Keys.Slip39: [extendable] :: EncryptedMasterSecret -> !Bool
+ Haskoin.Crypto.Keys.Slip39: [groupCount] :: Share -> !Int
+ Haskoin.Crypto.Keys.Slip39: [groupIndex] :: Share -> !Int
+ Haskoin.Crypto.Keys.Slip39: [groupThreshold] :: Scheme -> !Int
+ Haskoin.Crypto.Keys.Slip39: [groups] :: Scheme -> ![(Int, Int)]
+ Haskoin.Crypto.Keys.Slip39: [identifier] :: EncryptedMasterSecret -> !Int
+ Haskoin.Crypto.Keys.Slip39: [iterationExponent] :: EncryptedMasterSecret -> !Int
+ Haskoin.Crypto.Keys.Slip39: [memberIndex] :: Share -> !Int
+ Haskoin.Crypto.Keys.Slip39: [memberThreshold] :: Share -> !Int
+ Haskoin.Crypto.Keys.Slip39: [randomShares] :: SplitRandom -> ![ByteString]
+ Haskoin.Crypto.Keys.Slip39: [value] :: Share -> !ByteString
+ Haskoin.Crypto.Keys.Slip39: combineMnemonics :: Text -> [Text] -> Either String ByteString
+ Haskoin.Crypto.Keys.Slip39: combineShares :: [Share] -> Either String EncryptedMasterSecret
+ Haskoin.Crypto.Keys.Slip39: customizationString :: Bool -> [Int]
+ Haskoin.Crypto.Keys.Slip39: data EncryptedMasterSecret
+ Haskoin.Crypto.Keys.Slip39: data Scheme
+ Haskoin.Crypto.Keys.Slip39: data Share
+ Haskoin.Crypto.Keys.Slip39: data SplitRandom
+ Haskoin.Crypto.Keys.Slip39: decodeShare :: Text -> Either String Share
+ Haskoin.Crypto.Keys.Slip39: decryptMasterSecret :: Text -> EncryptedMasterSecret -> Either String ByteString
+ Haskoin.Crypto.Keys.Slip39: decryptMasterSecretBytes :: ByteString -> EncryptedMasterSecret -> Either String ByteString
+ Haskoin.Crypto.Keys.Slip39: digestIndex :: Word8
+ Haskoin.Crypto.Keys.Slip39: encodeShare :: Share -> Either String Text
+ Haskoin.Crypto.Keys.Slip39: encryptMasterSecret :: Int -> Bool -> Int -> Text -> ByteString -> Either String EncryptedMasterSecret
+ Haskoin.Crypto.Keys.Slip39: generateMnemonics :: MonadIO m => Scheme -> Bool -> Int -> Text -> ByteString -> m (Either String [[Text]])
+ Haskoin.Crypto.Keys.Slip39: generateShares :: MonadIO m => Scheme -> Bool -> Int -> Text -> ByteString -> m (Either String [[Share]])
+ Haskoin.Crypto.Keys.Slip39: generateSharesFrom :: Monad m => (Int -> m ByteString) -> Scheme -> Bool -> Int -> Text -> ByteString -> m (Either String [[Share]])
+ Haskoin.Crypto.Keys.Slip39: generateSharesWith :: Scheme -> Int -> Bool -> Int -> Text -> ByteString -> [SplitRandom] -> Either String [[Share]]
+ Haskoin.Crypto.Keys.Slip39: gfDiv :: Word8 -> Word8 -> Maybe Word8
+ Haskoin.Crypto.Keys.Slip39: gfInv :: Word8 -> Maybe Word8
+ Haskoin.Crypto.Keys.Slip39: gfMul :: Word8 -> Word8 -> Word8
+ Haskoin.Crypto.Keys.Slip39: indicesToMnemonic :: [Int] -> Either String Text
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Classes.Eq Haskoin.Crypto.Keys.Slip39.EncryptedMasterSecret
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Classes.Eq Haskoin.Crypto.Keys.Slip39.Scheme
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Classes.Eq Haskoin.Crypto.Keys.Slip39.Share
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Classes.Eq Haskoin.Crypto.Keys.Slip39.SplitRandom
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Classes.Ord Haskoin.Crypto.Keys.Slip39.Share
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Internal.Show.Show Haskoin.Crypto.Keys.Slip39.EncryptedMasterSecret
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Internal.Show.Show Haskoin.Crypto.Keys.Slip39.Scheme
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Internal.Show.Show Haskoin.Crypto.Keys.Slip39.Share
+ Haskoin.Crypto.Keys.Slip39: instance GHC.Internal.Show.Show Haskoin.Crypto.Keys.Slip39.SplitRandom
+ Haskoin.Crypto.Keys.Slip39: interpolate :: Word8 -> [(Word8, ByteString)] -> Either String ByteString
+ Haskoin.Crypto.Keys.Slip39: mnemonicToIndices :: Text -> Either String [Int]
+ Haskoin.Crypto.Keys.Slip39: recoverSecret :: Int -> [(Word8, ByteString)] -> Either String ByteString
+ Haskoin.Crypto.Keys.Slip39: rs1024Checksum :: [Int] -> [Int] -> [Int]
+ Haskoin.Crypto.Keys.Slip39: rs1024Polymod :: [Int] -> Int
+ Haskoin.Crypto.Keys.Slip39: rs1024Verify :: [Int] -> [Int] -> Bool
+ Haskoin.Crypto.Keys.Slip39: secretIndex :: Word8
+ Haskoin.Crypto.Keys.Slip39: slip39WordList :: Vector Text
+ Haskoin.Crypto.Keys.Slip39: slip39WordListMap :: Map Text Int
+ Haskoin.Crypto.Keys.Slip39: splitRandomSizes :: Int -> Int -> (Int, Int, Int)
+ Haskoin.Crypto.Keys.Slip39: splitSecret :: Int -> Int -> ByteString -> SplitRandom -> Either String [(Word8, ByteString)]
+ Haskoin.Network.Common: getVarIntListMax :: MonadGet m => String -> Word64 -> m a -> m [a]
+ Haskoin.Network.Common: maxAddrSize :: Word64
+ Haskoin.Network.Common: maxFilterHashes :: Word64
+ Haskoin.Network.Common: maxHeadersSize :: Word64
+ Haskoin.Network.Common: maxInvSize :: Word64
+ Haskoin.Network.Common: maxLocatorSize :: Word64
+ Haskoin.Network.CompactBlock: getBlockTxn :: MonadGet m => Network -> m BlockTxn
+ Haskoin.Network.CompactBlock: getCompactBlock :: MonadGet m => Network -> m CompactBlock
+ Haskoin.Network.CompactBlock: getGetBlockTxn :: MonadGet m => Network -> m GetBlockTxn
+ Haskoin.Network.Data: [maxCompactBlockIndex] :: Network -> !Word32
+ Haskoin.Network.Data: [maxProtocolMessageSize] :: Network -> !Word32
+ Haskoin.Network.Data: [minimumChainWork] :: Network -> !Integer
+ Haskoin.Network.Data: getNetworkTx :: MonadGet m => Network -> m Tx
+ Haskoin.Network.Message: maxPayloadSize :: Network -> MessageCommand -> Word32
+ Haskoin.Script.SigHash: instance GHC.Internal.Enum.Bounded Haskoin.Script.SigHash.SigHashFlag
+ Haskoin.Script.Standard: maxRedeemScriptSize :: Int
+ Haskoin.Script.Standard: maxWitnessScriptSize :: Int
+ Haskoin.Script.Standard: p2shScriptHash :: Script -> Either String Hash160
+ Haskoin.Script.Standard: p2wshScriptHash :: Script -> Either String Hash256
+ Haskoin.Transaction.Builder: dustThreshold :: OutputKind -> Word64
+ Haskoin.Transaction.Builder: maxStandardTxWeight :: Int
+ Haskoin.Transaction.Common: decodeTx :: ByteString -> Either String Tx
+ Haskoin.Transaction.Psbt: InputCoin :: !TxOut -> !Bool -> InputCoin
+ Haskoin.Transaction.Psbt: [coin] :: InputCoin -> !TxOut
+ Haskoin.Transaction.Psbt: [fromPreviousTx] :: InputCoin -> !Bool
+ Haskoin.Transaction.Psbt: [requirePreviousTx] :: Signer -> Bool
+ Haskoin.Transaction.Psbt: data InputCoin
+ Haskoin.Transaction.Psbt: inputCoins :: Psbt -> Either String [Maybe InputCoin]
+ Haskoin.Transaction.Psbt: instance Control.DeepSeq.NFData Haskoin.Transaction.Psbt.InputCoin
+ Haskoin.Transaction.Psbt: instance GHC.Classes.Eq Haskoin.Transaction.Psbt.InputCoin
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Generics.Generic Haskoin.Transaction.Psbt.InputCoin
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Read.Read Haskoin.Transaction.Psbt.InputCoin
+ Haskoin.Transaction.Psbt: instance GHC.Internal.Show.Show Haskoin.Transaction.Psbt.InputCoin
+ Haskoin.Transaction.Psbt: psbtFee :: Psbt -> Either String Word64
+ Haskoin.Transaction.Psbt: psbtPartialTransaction :: Psbt -> Either String Tx
+ Haskoin.Transaction.Taproot: [opaqueScript] :: ScriptPathData -> Maybe ByteString
+ Haskoin.Transaction.Taproot: checkTapTree :: MAST -> Either String ()
+ Haskoin.Transaction.Taproot: instance GHC.Internal.Records.HasField "hash" Haskoin.Transaction.Taproot.TaprootSignature Haskoin.Script.SigHash.SigHash
+ Haskoin.Transaction.Taproot: instance GHC.Internal.Records.HasField "sig" Haskoin.Transaction.Taproot.TaprootSignature Crypto.Secp256k1.Internal.Base.SchnorrSig
+ Haskoin.Transaction.Taproot: leafHashBytes :: TapLeafVersion -> ByteString -> Digest SHA256
+ Haskoin.Transaction.Taproot: pattern TaprootSignature :: SchnorrSig -> SigHash -> TaprootSignature
+ Haskoin.Transaction.Taproot: scriptPathScriptBytes :: ScriptPathData -> ByteString
+ Haskoin.Transaction.Taproot: taprootSignature :: SchnorrSig -> SigHash -> Either String TaprootSignature
+ Haskoin.Util.Arbitrary.Address: arbitraryTaprootAddress :: Gen Address
+ Haskoin.Util.Arbitrary.Address: arbitraryTokenAddress :: Gen Address
+ Haskoin.Util.Arbitrary.Block: arbitraryNetworkTx :: Network -> Ctx -> Gen Tx
+ Haskoin.Util.Arbitrary.Keys: arbitraryKeyPairC :: Ctx -> Gen (PrivateKey, PublicKey)
+ Haskoin.Util.Arbitrary.Message: arbitraryNetworkGetBlockTxn :: Network -> Gen GetBlockTxn
+ Haskoin.Util.Arbitrary.Message: arbitraryPositionsUpTo :: Word32 -> Gen [Word32]
+ Haskoin.Util.Arbitrary.Script: arbitraryP2SHMSOutput :: Ctx -> Gen ScriptOutput
+ Haskoin.Util.Arbitrary.Script: arbitraryP2SHMSOutputC :: Ctx -> Gen ScriptOutput
+ Haskoin.Util.Arbitrary.Script: arbitraryP2SHMSParam :: Gen (Int, Int)
+ Haskoin.Util.Arbitrary.Script: arbitraryP2SHMSParamC :: Gen (Int, Int)
+ Haskoin.Util.Arbitrary.Script: arbitrarySH32Output :: Gen ScriptOutput
+ Haskoin.Util.Arbitrary.Script: maxP2SHMultisigKeys :: Int
+ Haskoin.Util.Arbitrary.Script: maxP2SHMultisigKeysC :: Int
+ Haskoin.Util.Arbitrary.Transaction: arbitraryAnyInputOf :: Gen (PrivateKey, PublicKey) -> Network -> Ctx -> Bool -> Gen (SigInput, PrivateKey)
+ Haskoin.Util.Arbitrary.Transaction: arbitraryMSSigInputOf :: Gen (Int, Int) -> Network -> Ctx -> Gen (SigInput, [PrivateKey])
+ Haskoin.Util.Arbitrary.Transaction: arbitraryMSSigInputWith :: Gen (PrivateKey, PublicKey) -> Gen (Int, Int) -> Network -> Gen (SigInput, [PrivateKey])
+ Haskoin.Util.Marshal: marshalGetAll :: (Marshal s a, MonadGet m) => s -> m a
- Haskoin.Address: outputAddress :: Ctx -> ScriptOutput -> Maybe Address
+ Haskoin.Address: outputAddress :: Network -> Ctx -> ScriptOutput -> Maybe Address
- Haskoin.Address: payToNestedScriptAddress :: Ctx -> ScriptOutput -> Address
+ Haskoin.Address: payToNestedScriptAddress :: Ctx -> ScriptOutput -> Either String Address
- Haskoin.Address: payToScriptAddress :: Ctx -> ScriptOutput -> Address
+ Haskoin.Address: payToScriptAddress :: Ctx -> ScriptOutput -> Either String Address
- Haskoin.Address: payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Address
+ Haskoin.Address: payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Either String Address
- Haskoin.Address: scriptToAddress :: Ctx -> Script -> Either String Address
+ Haskoin.Address: scriptToAddress :: Network -> Ctx -> Script -> Either String Address
- Haskoin.Address: scriptToAddressBS :: Ctx -> ByteString -> Either String Address
+ Haskoin.Address: scriptToAddressBS :: Network -> Ctx -> ByteString -> Either String Address
- Haskoin.Address.CashAddr: cash32encode :: CashPrefix -> ByteString -> Cash32
+ Haskoin.Address.CashAddr: cash32encode :: CashPrefix -> ByteString -> Maybe Cash32
- Haskoin.Block.Merkle: calcHash :: Int -> Int -> [TxHash] -> Hash256
+ Haskoin.Block.Merkle: calcHash :: Int -> Int -> [TxHash] -> Maybe Hash256
- Haskoin.Crypto.Keys.Extended: deriveMSAddr :: Ctx -> [XPubKey] -> Int -> KeyIndex -> (Address, RedeemScript)
+ Haskoin.Crypto.Keys.Extended: deriveMSAddr :: Ctx -> [XPubKey] -> Int -> KeyIndex -> Either String (Address, RedeemScript)
- Haskoin.Crypto.Keys.Extended: deriveMSAddrs :: Ctx -> [XPubKey] -> Int -> KeyIndex -> [(Address, RedeemScript, KeyIndex)]
+ Haskoin.Crypto.Keys.Extended: deriveMSAddrs :: Ctx -> [XPubKey] -> Int -> KeyIndex -> Either String [(Address, RedeemScript, KeyIndex)]
- Haskoin.Crypto.Keys.Extended: derivePathMSAddr :: Ctx -> [XPubKey] -> SoftPath -> Int -> KeyIndex -> (Address, RedeemScript)
+ Haskoin.Crypto.Keys.Extended: derivePathMSAddr :: Ctx -> [XPubKey] -> SoftPath -> Int -> KeyIndex -> Either String (Address, RedeemScript)
- Haskoin.Crypto.Keys.Extended: derivePathMSAddrs :: Ctx -> [XPubKey] -> SoftPath -> Int -> KeyIndex -> [(Address, RedeemScript, KeyIndex)]
+ Haskoin.Crypto.Keys.Extended: derivePathMSAddrs :: Ctx -> [XPubKey] -> SoftPath -> Int -> KeyIndex -> Either String [(Address, RedeemScript, KeyIndex)]
- Haskoin.Network.Common: Version :: !Word32 -> !Word64 -> !Word64 -> !NetworkAddress -> !NetworkAddress -> !Word64 -> !VarString -> !Word32 -> !Bool -> Version
+ Haskoin.Network.Common: Version :: !Int32 -> !Word64 -> !Int64 -> !NetworkAddress -> !NetworkAddress -> !Word64 -> !VarString -> !Int32 -> !Bool -> Version
- Haskoin.Network.Common: [startHeight] :: Version -> !Word32
+ Haskoin.Network.Common: [startHeight] :: Version -> !Int32
- Haskoin.Network.Common: [timestamp] :: Version -> !Word64
+ Haskoin.Network.Common: [timestamp] :: Version -> !Int64
- Haskoin.Network.Common: [version] :: Version -> !Word32
+ Haskoin.Network.Common: [version] :: Version -> !Int32
- 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.Network.Data: Network :: !String -> !Word8 -> !Word8 -> !Word8 -> !Word32 -> !Word32 -> !Word32 -> !BlockHeader -> !Tx -> !Int -> !Word32 -> !Word32 -> !Word32 -> !Word64 -> !ByteString -> !Int -> !Bool -> !Bool -> !Bool -> !Integer -> !BlockHeight -> !Maybe BlockHash -> !BlockHeight -> !BlockHeight -> !Word32 -> !Word32 -> ![(BlockHeight, BlockHash)] -> !Integer -> !Word32 -> ![String] -> !Maybe Word32 -> !Maybe Word32 -> !Maybe Word32 -> !Maybe Word32 -> !Integer -> !Bool -> !Maybe Text -> !Maybe Text -> !Bool -> !Word32 -> Network
- Haskoin.Script.Standard: toP2SH :: Script -> ScriptOutput
+ Haskoin.Script.Standard: toP2SH :: Script -> Either String ScriptOutput
- Haskoin.Script.Standard: toP2WSH :: Script -> ScriptOutput
+ Haskoin.Script.Standard: toP2WSH :: Script -> Either String ScriptOutput
- Haskoin.Transaction.Builder: guessTxFee :: Word64 -> [InputKind] -> [OutputKind] -> Word64
+ Haskoin.Transaction.Builder: guessTxFee :: Word64 -> [InputKind] -> [OutputKind] -> Either String Word64
- Haskoin.Transaction.Psbt: Signer :: (PublicKey -> Derivation -> Maybe SecKey) -> (Hash160 -> Maybe PrivateKey) -> [SigHash] -> Signer
+ Haskoin.Transaction.Psbt: Signer :: (PublicKey -> Derivation -> Maybe SecKey) -> (Hash160 -> Maybe PrivateKey) -> [SigHash] -> Bool -> Signer
- Haskoin.Transaction.Taproot: ScriptPathData :: Maybe ByteString -> [ByteString] -> Script -> Bool -> Word8 -> PubKey -> [Digest SHA256] -> ScriptPathData
+ Haskoin.Transaction.Taproot: ScriptPathData :: Maybe ByteString -> [ByteString] -> Script -> Bool -> Word8 -> PubKey -> [Digest SHA256] -> Maybe ByteString -> ScriptPathData
- Haskoin.Transaction.Taproot: encodeTaprootWitness :: Ctx -> TaprootWitness -> WitnessStack
+ Haskoin.Transaction.Taproot: encodeTaprootWitness :: Ctx -> TaprootWitness -> Either String WitnessStack
Files
- CHANGELOG.md +133/−1
- README.md +833/−16
- data/bip341.json +1/−1
- data/bip39_vectors.json +1754/−0
- data/bip39_wordlist.txt +2048/−0
- data/core_signed_txs.json +685/−0
- data/key_io_invalid.json +88/−58
- data/key_io_valid.json +326/−252
- data/psbt_complex_core32.json +18/−0
- data/slip39_split.json +324/−0
- data/slip39_vectors.json +406/−0
- data/slip39_wordlist.txt +1024/−0
- data/taproot_mainnet_spends.json +295/−0
- haskoin-core.cabal +13/−1
- src/Haskoin/Address.hs +167/−32
- src/Haskoin/Address/Bech32.hs +3/−0
- src/Haskoin/Address/CashAddr.hs +49/−10
- src/Haskoin/Block/Common.hs +11/−3
- src/Haskoin/Block/Headers.hs +70/−22
- src/Haskoin/Block/Merkle.hs +110/−22
- src/Haskoin/Crypto/Keys/Common.hs +40/−19
- src/Haskoin/Crypto/Keys/Extended.hs +218/−99
- src/Haskoin/Crypto/Keys/Mnemonic.hs +128/−23
- src/Haskoin/Crypto/Keys/Slip39.hs +1898/−0
- src/Haskoin/Crypto/Signature.hs +22/−18
- src/Haskoin/Network/BlockFilter.hs +3/−1
- src/Haskoin/Network/Bloom.hs +102/−25
- src/Haskoin/Network/Common.hs +127/−31
- src/Haskoin/Network/CompactBlock.hs +68/−19
- src/Haskoin/Network/Constants.hs +28/−2
- src/Haskoin/Network/Data.hs +38/−4
- src/Haskoin/Network/Message.hs +39/−19
- src/Haskoin/Script/Common.hs +8/−0
- src/Haskoin/Script/SigHash.hs +104/−11
- src/Haskoin/Script/Standard.hs +129/−10
- src/Haskoin/Transaction/Builder.hs +355/−81
- src/Haskoin/Transaction/Builder/Sign.hs +164/−38
- src/Haskoin/Transaction/Common.hs +34/−15
- src/Haskoin/Transaction/Psbt.hs +601/−192
- src/Haskoin/Transaction/Taproot.hs +241/−64
- src/Haskoin/Util/Arbitrary/Address.hs +26/−2
- src/Haskoin/Util/Arbitrary/Block.hs +56/−15
- src/Haskoin/Util/Arbitrary/Keys.hs +7/−0
- src/Haskoin/Util/Arbitrary/Message.hs +29/−12
- src/Haskoin/Util/Arbitrary/Network.hs +9/−2
- src/Haskoin/Util/Arbitrary/Script.hs +82/−17
- src/Haskoin/Util/Arbitrary/Transaction.hs +141/−39
- src/Haskoin/Util/Helpers.hs +61/−18
- src/Haskoin/Util/Marshal.hs +15/−2
- test/Haskoin/Address/Bech32Spec.hs +46/−4
- test/Haskoin/Address/CashAddrSpec.hs +42/−2
- test/Haskoin/AddressSpec.hs +205/−27
- test/Haskoin/BlockSpec.hs +348/−12
- test/Haskoin/Crypto/Keys/ExtendedSpec.hs +171/−11
- test/Haskoin/Crypto/Keys/MnemonicSpec.hs +166/−3
- test/Haskoin/Crypto/Keys/Slip39Spec.hs +762/−0
- test/Haskoin/Crypto/KeysSpec.hs +132/−94
- test/Haskoin/Crypto/SignatureSpec.hs +55/−6
- test/Haskoin/NetworkSpec.hs +388/−5
- test/Haskoin/ScriptSpec.hs +248/−37
- test/Haskoin/Transaction/PsbtSpec.hs +735/−59
- test/Haskoin/Transaction/TaprootSpec.hs +451/−23
- test/Haskoin/TransactionSpec.hs +586/−49
- test/Haskoin/UtilSpec.hs +71/−4
CHANGELOG.md view
@@ -5,6 +5,138 @@ 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.6.0] - 2026-11-06++### Added++- `Haskoin.Crypto.Keys.Slip39`: Shamir's secret-sharing for mnemonic codes, SLIP-0039, as a standalone module. `generateMnemonics` splits a master secret, encrypted with a passphrase, into share mnemonics under a two-level scheme of groups and members, and `combineMnemonics` rebuilds it from a threshold of them. `generateShares` and `generateMnemonics` draw their random bytes from the operating system, through `getEntropy` of the entropy package; `generateSharesFrom` takes the source of random bytes as an argument, and `generateSharesWith` the identifier and the random values themselves, so that a split can be reproduced. `encodeShare` and `decodeShare` read and write one share, with its checksum, padding and ranges checked as the specification requires; a share has at most 1023 words, the length the checksum covers, which is a value of 1270 bytes. `encryptMasterSecret` and `decryptMasterSecret` are the passphrase step, and both refuse a passphrase outside printable ASCII, as the specification requires; `decryptMasterSecretBytes` takes the passphrase as unchecked bytes, to recover shares of software that allowed other characters. `splitSecret`, `recoverSecret` and `interpolate` are the sharing over GF(256), and `rs1024Checksum` the checksum. No error text and no `Show` instance holds a word or a value of a share: an unknown word is named by its position. The 45 vectors of the reference implementation, a share set of Sparrow's library, and split cases with fixed random values made by an independent Python implementation of the specification, `test/Haskoin/Crypto/Keys/slip39_verify.py`, check it. The word list is `data/slip39_wordlist.txt`, the file of the specification, and its SHA-256 is checked.+- `tryDerivePath` and `tryDerivePubPath` in `Haskoin.Crypto.Keys.Extended` derive along a path and return an error value when the path cannot be followed. Use them for a path that comes from outside, such as the path of a PSBT record. `derivePath` and `derivePubPath` throw `DerivationException` in that case, as before.+- `maxRedeemScriptSize` (520) and `maxWitnessScriptSize` (10,000) in `Haskoin.Script.Standard` are the largest scripts that a pay-to-script-hash and a pay-to-witness-script-hash output can commit to and still be spent: `MAX_SCRIPT_ELEMENT_SIZE` and `MAX_SCRIPT_SIZE` of Bitcoin Core. `p2shScriptHash` and `p2wshScriptHash` give the hash of a script for those outputs, and fail above the limit.+- `p2shP2wpkhAddr` in `Haskoin.Address` is the P2SH-P2WPKH address of a public key hash.+- `SigHashFlag` has a `Bounded` instance.+- `decodeTx` in `Haskoin.Transaction.Common` reads a transaction from bytes that hold the transaction and nothing else, as `DecodeHexTx` requires in Bitcoin Core. `deserialize` reads a transaction from the front of a longer input, as a block needs it to, and does not notice bytes that follow. Use `decodeTx` for a transaction that arrives on its own.+- `dustThreshold` in `Haskoin.Transaction.Builder` gives the amount below which nodes do not relay an output of a kind, as `GetDustThreshold` computes it in Bitcoin Core at the default dust relay fee: 546 satoshi for pay-to-public-key-hash, 294 for pay-to-witness-public-key-hash, 330 for pay-to-witness-script-hash and Taproot. `chooseCoins` can return a change below it, and its documentation now says to leave such a change to the fee.+- `maxStandardTxWeight` (400,000) in `Haskoin.Transaction.Builder` is the weight above which nodes do not relay a transaction: `MAX_STANDARD_TX_WEIGHT` of Bitcoin Core, and the 100,000 bytes of `MAX_STANDARD_TX_SIZE` of Bitcoin Cash Node.+- `data/core_signed_txs.json` holds 97 transactions that Bitcoin Core 32.1 signed on regtest, one for each of 16 kinds of coin and each hash type. The tests verify them with `verifyStdTx`, so that the verifier is checked against signatures the library did not make.+- `arbitraryKeyPairC` generates a key pair in compressed form.+- `maxPayloadSize` in `Haskoin.Network.Message` gives the largest payload a message of a command may have on a network, and `getMessage` applies it. On Bitcoin Cash every message but `block`, `cmpctblock` and `blocktxn` is limited to 2 MiB, the new `maxProtocolMessageSize` of `Network`, as `CMessageHeader::IsOversized` does in Bitcoin Cash Node. The limit was 64 MB for every message, and one 33.5 MB `getblocktxn` took 19 seconds and 5.7 GiB to parse. A node should use `maxPayloadSize` on the message header before it buffers a payload.+- `getCompactBlock`, `getGetBlockTxn` and `getBlockTxn` in `Haskoin.Network.CompactBlock` read the compact block messages of a network. On Bitcoin a transaction position above 65535, the new `maxCompactBlockIndex` of `Network`, fails the parse, and so does a compact block of more than 65535 transactions, as in BIP 152 and Bitcoin Core. Bitcoin Cash keeps 32 bits, as Bitcoin Cash Node. `getMessage` uses them.+- `getNetworkTx` in `Haskoin.Network.Data` reads a transaction as a peer of the network sends it: without the SegWit format on a network that has no SegWit. `getMessage` uses it for `tx`, `block`, `blocktxn` and `cmpctblock`.+- `minimumChainWork` in `Network` is the `nMinimumChainWork` of Bitcoin Core 32.1 and of Bitcoin Cash Node. Once the best block has that work, `connectBlock` and `connectBlocks` refuse a new header whose chain has less, which bounds cheap forks from far below the tip. A node that is still syncing below that work is not protected by this.+- `getVarIntListMax`, and the limits `maxInvSize` (50000), `maxAddrSize` (1000), `maxHeadersSize` (2000), `maxLocatorSize` (101) and `maxFilterHashes` (2000) in `Haskoin.Network.Common`. The parsers of `inv`, `getdata`, `addr`, `addrv2`, `headers`, `getheaders`, `getblocks` and `cfheaders` fail above them, as soon as the count is read.+- `extractIndexedMatches` and `merkleBlockIndexedTxs` in `Haskoin.Block.Merkle` give the position of each matching transaction of a partial Merkle tree. `maxMerkleFlagBits` is the number of flag bits a valid proof can use.+- `leafHashBytes` in `Haskoin.Transaction.Taproot` is the tapleaf hash of a script given as bytes, for a script that is not a list of opcodes. `checkTapTree` tells whether every leaf of a script tree can be spent: it fails for a node deeper than 128, which no control block can prove, and for a leaf version that is odd or is `0x50`. Run it on a tree before you give out its address. `scriptPathScriptBytes` gives the script bytes of a script path spend as they are in the witness. `taprootSignature` builds a `TaprootSignature` and fails for a hash type that Taproot does not define.+- `data/taproot_mainnet_spends.json` holds 24 Taproot script path spends from Bitcoin mainnet blocks 840000 and 900000, with scripts of 34 to 635 bytes. The tests check that each one is read and that its script and control block commit to the output key of the coin it spends.+- `requirePreviousTx` on a PSBT `Signer`. With it, the signer refuses a SegWit version 0 coin that is known from its witness output only, since the amount of such a coin is a claim of whoever wrote the PSBT (CVE-2020-14199). `keySigner` and `xPrvSigner` set it.+- `inputCoins` and `psbtFee`: the coin of each PSBT input with whether it comes from the previous transaction, and the fee when every coin does.+- `psbtPartialTransaction`: the transaction of a PSBT that is still being signed.+- `data/psbt_complex_core32.json`: nine coins of every kind, signed by Bitcoin Core 32.1 without finalizing.++### Changed++- `unmarshal` and `unmarshalLazy` fail when bytes are left over after the value. They used to read a value from the start of the input and ignore the rest, so two different byte strings could read as the same public key, signature or extended key. Code that reads a value from the start of a longer stream must run `marshalGet` itself.+- The binary form of `XPubKey` holds a compressed public key in exactly 33 bytes, as BIP 32 defines it and as `xPubImport` already required. A key in the 65-byte form used to be read.+- `decodeStrictSig`, and with it the binary and JSON forms of `Sig`, apply the encoding rule of BIP 66 and nothing more. A signature with a high S value is now read. Low S is a relay rule of Bitcoin Core (`SCRIPT_VERIFY_LOW_S`), not a consensus rule, and blocks hold signatures with a high S. `isCanonicalHalfOrder` tests the relay rule. The form of a transaction signature, `TxSignature`, still applies it. The signers make low-S signatures, as before.+- `makeXPrvKey` throws `DerivationException` for a seed that is not 16 to 64 bytes long, the range that BIP 32 gives and that Bitcoin Core enforces. A seed of any length used to give a master key, the empty string included, and anyone can compute the keys of such a wallet. A wallet that was made from a seed of another length can still be opened without this function: its master key is `XPrvKey 0 "00000000" 0 chain key`, where `key` and `chain` are the two halves of `hmac512 "Bitcoin seed" seed`.+- `applyPath` returns `Left` for a path that leads below depth 255, which no key can have. It used to throw `DerivationException`, although its type promises an error value.+- The path constructors `:|` and `:/` throw `DerivationException` for an index of 2^31 or more. Such a path could be built before, but its text and binary forms did not read back to the same path, and deriving along it threw. The two constructors are now pattern synonyms. Code that builds and matches paths with them compiles as before.+- `fromMiniKey` requires a mini key to start with the letter `S` and to hold only characters of the Base58 alphabet, as the format defines. One arbitrary string of 22 or 30 characters in 256 used to be read as a private key.+- `getInteger` reads only the encoding that `putInteger` writes: a tag byte of 0 or 1, a sign byte of 0x01 or 0xff, and the shortest bytes that hold the number. Several other byte strings used to read as the same number. Data written by `putInteger` reads as before. The reader also takes close to linear time now. A number of 400 KB used to take 54 seconds.+- `getIntMap` refuses keys that are not in ascending order, which is how `putIntMap` writes them. A key given twice used to be read, and its second element silently replaced the first.+- `convertBits` returns `Nothing` for a width that is zero or negative, where it used to loop forever, and for a digit that does not fit the width it is converted from, whose high bits it used to drop.+- `bech32Encode` returns `Nothing` for a five-bit word above 31. It used to raise an array index error.+- The JSON parser of `Sig` no longer copies the text it refuses into its error message.+- `data/key_io_valid.json` and `data/key_io_invalid.json` are the files of Bitcoin Core 32.1, with its Bech32m and testnet4 vectors. The tests run every vector through `textToAddr` and `fromWif`, as `key_io_tests.cpp` does.+- `payToScriptAddress`, `payToWitnessScriptAddress` and `payToNestedScriptAddress` return `Either String Address`, and `toP2SH` and `toP2WSH` return `Either String ScriptOutput`. They fail for a redeem script above 520 bytes and for a witness script above 10,000 bytes. A pay-to-script-hash redeem script is a push of the spending input, and consensus limits a push to 520 bytes, so coins sent to the hash of a longer script can never be spent. `payToScriptAddress` used to give an address for a multisig of 16 to 20 compressed keys (547 to 684 bytes), or of 8 or more uncompressed keys, and Bitcoin Core rejects every spend of such a coin with "Push value size limit exceeded". Fifteen compressed keys fit, and twenty still fit a pay-to-witness-script-hash output. These functions also return the failure of `encodeOutput` for a multisig that no script can express, where `payToScriptAddress` used to raise an `error` call.+- `deriveMSAddr` and `derivePathMSAddr` return `Either String (Address, RedeemScript)`, and `deriveMSAddrs` and `derivePathMSAddrs` return `Either String` of their list. They fail for more than 15 keys, whose address would be unspendable as described above, and for a number of required signatures that is not between 1 and the number of keys, which used to raise an `error` call when the address was forced. Coins that were sent to an address these functions gave for 16 to 20 keys are lost: no spend of them is valid.+- `scriptToAddress`, `scriptToAddressBS` and `outputAddress` take the `Network` as their first argument and give an address only for an output kind that the network has. A witness program has no address on Bitcoin Cash, and a 256-bit script hash (`OP_HASH256 <32 bytes> OP_EQUAL`) has none on Bitcoin, where Bitcoin Core reports that script as non-standard. Without the network these functions returned an address that has no text form on the chain it came from, and the JSON encoder of `Address` throws for such an address. One such output is in Bitcoin block 211997 (transaction `a4bfa8ab6435ae5f25dae9d89e4eb67dfa94283ca751f393c1ddc5a837bbc31b`, output 0), so an indexer that encoded the addresses of outputs failed on that transaction. Every address these functions return now has a text form on the network.+- `decodeInput` only reads a pay-to-script-hash input whose redeem script is in the form `encodeOutput` writes. A multisig redeem script whose keys are pushed with `OP_PUSHDATA1` used to decode, and `inputAddress` then gave the hash of the re-encoded script, an address that never held the coin. Such an input now fails to decode, so `inputAddress` is always the hash of the redeem script bytes of the input, and encoding a decoded input never rewrites its redeem script. Spending such a script breaks the `MINIMALDATA` rule of the relay policy, so standard transactions are not affected.+- `decodeInput` reads a `NestedWitnessInput` only when the witness program is a direct push, as BIP 141 requires ("the scriptSig must be exactly a push of the BIP16 redeemScript"). A program pushed with `OP_PUSHDATA1` used to decode, although such an input is invalid by consensus.+- `inputAddress` gives no address for a redeem script above 520 bytes.+- `txSigHashForkId` writes the hash type as four bytes on a network without a fork id, as `SignatureHash` of Bitcoin Core does for a SegWit input. It used to keep the low byte only, while `txSigHash` wrote all four. Nothing changes for a hash type that fits in one byte, which is every hash type a signature can carry.+- `cashAddrDecode`, and with it `textToAddr` and the JSON parser of `Address` on Bitcoin Cash networks, refuse a string longer than the prefix of the network, the colon and 112 characters before any other work. No valid address is longer, since the largest hash of the format is 64 bytes. A string of 1 MB used to take 1.5 seconds and several hundred megabytes of memory, and now takes less than a millisecond.+- `cash32decode` and `cash32decodeType` refuse a prefix that is not made of letters only, as the decoder of Bitcoin Cash Node does. A digit or a second colon in the prefix used to be read.+- `cash32encode` returns `Maybe Cash32`. It fails for a prefix that is empty or not made of ASCII letters, and it writes an uppercase prefix in lowercase. It used to write strings that `cash32decode` refuses: with an empty prefix, a digit, a colon, or a prefix of another case than the data.+- The `Read` instance of `Address` goes through `witnessAddress` and `cashAddress` for the two generic kinds. The derived instance read a witness version above 16, a program of any length and a CashAddr type above 15, which no other function builds.+- `enumFrom`, `enumFromTo`, `enumFromThen`, `enumFromThenTo`, `succ` and `pred` of `SigHashFlag` walk the five flags in the order of their constructors. `[SIGHASH_ALL ..]` and `succ SIGHASH_SINGLE` used to raise "Not a valid sighash flag", because the values of the flags are not consecutive.+- The generators `arbitraryMulSigSHInput`, `arbitraryMulSigSHInputC`, `arbitraryMulSigSHInputFull` and `arbitraryMulSigSHInputFullC` draw a redeem script of at most 520 bytes. More than half of the inputs they drew could not exist. `arbitraryP2SHMSParam`, `arbitraryP2SHMSParamC`, `arbitraryP2SHMSOutput` and `arbitraryP2SHMSOutputC` are the new generators behind them. `arbitrarySHSigInput` and `arbitraryPartialTxs` use at most 7 keys for the same reason.+- `arbitraryScriptOutput` draws a 256-bit script hash output on networks that have CashAddr, and `arbitraryScriptInput` draws a nested witness input only on networks that have SegWit. `arbitraryBitcoinAddress` draws a Taproot address one time in six, through the new `arbitraryTaprootAddress`, and `arbitraryBitcoinCashAddress` a payable token-aware address one time in five, through the new `arbitraryTokenAddress`.+- `makeSignature`, and with it `signTx`, `signInput`, `signNestedWitnessTx` and `signPsbt`, refuse a hash type that no node accepts, with the reason: one that is not `SIGHASH_ALL`, `SIGHASH_NONE` or `SIGHASH_SINGLE` with or without `SIGHASH_ANYONECANPAY` (the test of `IsDefinedHashtypeSignature` in Bitcoin Core), the fork id flag on a network without a fork id, and a value that does not fit in the one byte a signature carries. On Bitcoin Cash this refuses `SIGHASH_UTXOS` (0x20), whose digest the library does not compute. The types 0x00, 0x04, 0x21, 0x41 and 0x80 used to be signed, and the result was a transaction that nodes do not relay. A type with upper bytes set, such as 0x101, gave a signature that does not verify. `signTx` still signs `SIGHASH_NONE` and `SIGHASH_SINGLE` when the `SigInput` asks for them: check the hash type of a `SigInput` that comes from someone else.+- `makeSignature` refuses a pay-to-script-hash coin without its redeem script, with a redeem script above 520 bytes, or with one that does not hash to the coin, and the same for a pay-to-witness-script-hash coin and its witness script, above 10,000 bytes. It used to sign whatever script it was given, and the output script itself when it was given none. Such a signature spends nothing.+- `makeSignature` refuses a SegWit coin, and `signTx` a Taproot coin, on a network without SegWit. `signTx bch` used to return a transaction with a witness, which no Bitcoin Cash node reads.+- `signTx`, `signNestedWitnessTx` and `sigKeys` refuse to sign a SegWit version 0 coin with an uncompressed key, whether the coin pays to the hash of the key or to a witness script that holds it. Nodes do not relay such a spend (`SCRIPT_VERIFY_WITNESS_PUBKEYTYPE` in Bitcoin Core), so the transaction they returned could not be broadcast. A block may still hold the spend. To make it on purpose, call `signInput` with the `PrivateKey` in its uncompressed form.+- `signInput` and `signNestedInput` refuse a key that is not a key of the coin: the key of a pay-to-public-key coin, the key whose hash the coin pays to, or a key of its multisig. They used to return an input that no node accepts.+- `signTaprootInput` refuses a secret key whose x-only public key is not the output key of the coin, and a spent output that is not a Taproot output. Passing the internal key in place of its tweak used to give a signature that does not verify.+- `verifyStdInput` and `verifyStdTx` require a multisig input to hold exactly as many signatures as its script requires, and no other item. `OP_CHECKMULTISIG` takes that many items as signatures, and the item below them must be empty (BIP 147), so no block can contain an input with an item after its signatures or with an empty item among them. They used to answer `True` for both, in bare, pay-to-script-hash and pay-to-witness-script-hash form. An empty item before the signatures is refused too: nodes do not relay it, and in a witness it is invalid.+- `verifyStdTx` refuses a transaction in which two inputs spend the same outpoint, as `CheckTransaction` does in Bitcoin Core (`bad-txns-inputs-duplicate`).+- `verifyStdInput` and `verifyStdTx` refuse a SegWit or Taproot input on a network without SegWit, and compare the input script of a nested SegWit input with the push of its program byte for byte.+- `verifyStdTx` looks up the coins once for the whole transaction. It used to walk the list of coins for every input, and to rebuild the list of spent outputs for every Taproot input, so its time grew with the cube of the number of Taproot inputs: 400 of them took 1.6 s and now take 0.14 s.+- `signTx` derives the public keys of its secret keys once for the whole transaction, not once per input. 400 Taproot inputs with 400 keys took 11.7 s and now take 0.2 s.+- `guessTxFee` returns `Either String Word64`. It fails when the fee does not fit in 64 bits, where it used to wrap around: 192 bytes at 2^58 satoshi per byte gave a fee of 0. It also fails for a kind of input or output that cannot exist, such as `InputMulSig 16 16`, whose redeem script would be 547 bytes, or a multisig of 3 of 2.+- `chooseCoins` and `chooseCoinsSink` add amounts as unbounded integers. A target near 2^64 used to wrap around with its fee, so that a coin of 1,000 satoshi "covered" a target of 2^64 - 101. They fail for a kind of input or output that cannot exist, and they stop with an error when the transaction would weigh more than `maxStandardTxWeight`. They keep the sums of the selection as coins are added: selecting 1,400 coins took 0.11 s and now takes under a millisecond.+- `greedyAddSink` adds amounts as unbounded integers as well. `chooseCoinsSink` no longer uses it.+- `mergeTxs` and `mergeTxInput` keep the witness of the transaction. They used to remove it, so a SegWit input that was signed before the multisig signers took their copies lost its signature. `mergeTxs` now answers "Transactions do not match" for copies whose witnesses differ, since it does not merge witnesses. `mergeTxInput` returns `Left` for an input index that one of the transactions does not have. It used to raise an error.+- `hasWitness` looks only at the stacks of the inputs. A witness item in a stack beyond the inputs used to turn the witness format on, which gave bytes with the marker and no witness item, a wrong weight and a wrong witness transaction id. Neither Bitcoin Core nor this library reads those bytes.+- The JSON form of `Tx` requires one witness stack per input, or none.+- `buildAddrTx` refuses an amount above the `maxSatoshi` of the network, as `createrawtransaction` refuses an amount out of range in Bitcoin Core.+- The generators of `Haskoin.Util.Arbitrary.Transaction` draw compressed keys for SegWit signing data, a pay-to-script-hash multisig `InputKind` of at most 15 keys, SegWit and Taproot signing data only on networks that have SegWit, and amounts of which a third are below 10,000 satoshi.+- `fromMnemonic` and `mnemonicToSeed` no longer put words of the sentence or checksum values into their error text. An unknown word is named by its position, as in `fromMnemonic: words 3 and 7 are not in the list`, and a failed checksum is `fromMnemonic: checksum failed`. A sentence typed in capitals used to be echoed whole, and the text of a `Left` ends up in logs.+- `fromMnemonic` and `mnemonicToSeed` do a bounded amount of work on text from an untrusted source. The sentence is split into words before anything is normalised, and a word longer than the eight letters of the longest list word is refused as it is. `mnemonicToSeed` refuses a passphrase whose NFKD form would hold more than 30 combining characters in a row, the limit of the Stream-Safe Text Format of Unicode (UAX #15), which no real text passes. 32 KB of combining characters used to take half a minute, and the time grew with the square of the length. Every sentence and passphrase that was accepted before, other than such a passphrase, gives the same seed. This adds a direct dependency on `unicode-data`, which `unicode-transforms` already used.+- The parser of `MerkleBlock` refuses more hashes than transactions and more flag bytes than the tree can use, before it reads them. A `merkleblock` message used to cost about 95 bytes of memory per flag byte, 1.5 GB for a 16 MB message. Proofs that verify are not affected.+- An empty witness item, and any empty byte string of the wire format, costs one list cell. A `tx` message with four million empty witness items kept 356 MB and now keeps 100 MB, as much as Bitcoin Core.+- `bloomRelevantUpdate` matches a transaction as BIP 37 and `IsRelevantAndUpdate` of Bitcoin Core do: by its hash, by the data of its output scripts, by the outpoints it spends and by the data of its input scripts. It only looked at outputs, so a transaction that spends a watched coin was not matched. Pay-to-pubkey and multisig outputs are told by the shape of the script, as `Solver` does, so a hybrid key or a key that is not on the curve counts. The `Ctx` argument is no longer used.+- The `version`, `timestamp` and `startHeight` fields of `Version` are `Int32`, `Int64` and `Int32`, signed as in Bitcoin Core. A peer that sent version ffffffff appeared to have version 4294967295.+- `validVersion` applies its three conditions independently, as `ContextualCheckBlockHeader` does. It accepted old block versions on networks whose activation heights are not in the order BIP 34, BIP 66, BIP 65, which are Bitcoin Cash regtest and testnet4.+- `chooseBest` keeps the first block when two have the same work, whatever their heights, as Bitcoin Core does. It preferred the higher one.+- `connectBlock` and `connectBlocks` return the stored node of a header that is already in the store, as `AcceptBlockHeader` does in Bitcoin Core. A known header at or below a checkpoint used to be refused as "Rewriting pre-checkpoint chain".+- The memory header store returns a node only for the hash that was asked for. It returned any node whose hash shared the first eight bytes.+- `calcHash` returns `Maybe Hash256` and no longer calls `error` outside the tree. `splitIn` with a length below one returns the list as one chunk and no longer loops. `Hashable BlockNode` hashes the header only, like `Eq`.+- An `addrv2` entry of the Tor v2 network is read whatever its length, since BIP 155 says to ignore such entries, and no longer fails the message. `fromNetworkAddressV2` gives `Nothing` for an IPv6 entry that holds an IPv4-mapped or an OnionCat address. `toNetworkAddressV2` gives a Tor v2 entry for an OnionCat address.+- `getMessage` checks the magic bytes before the command name.+- `bloomCreate` picks the number of hash functions with the integer division of Bitcoin Core.+- `Read MessageCommand` reads what `Show` writes for any bytes.+- `btcRegTest` has a target timespan of one day, as Bitcoin Core regtest, and `btcTest4` pins no BIP 34 block hash, as Bitcoin Core testnet4.+- The reader of `BlockNode` refuses a chain work that is negative or does not fit 256 bits.+- The generators `arbitraryMerkleBlock`, `arbitraryBlock`, `arbitraryBlockNode`, `arbitraryVarInt`, `arbitraryReject`, `arbitrarySendCompact`, `arbitraryCompactBlock` and `arbitraryBlockTxn` cover more cases, and `arbitraryNetworkTx`, `arbitraryNetworkGetBlockTxn` and `arbitraryPositionsUpTo` are new.+- A `TaprootSignature` only holds a hash type that `isTaprootSigHash` accepts. Its constructor is now a pattern that only matches. Build a value with `taprootSignature` or `decodeTaprootSig`; the fields `sig` and `hash` are read as before and cannot be updated. `encodeTaprootSig` used to cut any hash type to one byte, so `0x100` was written as a 65-byte signature ending in `00`, which BIP 341 forbids, and `0x101` as `SIGHASH_ALL`.+- `ScriptPathData` has a new last field, `opaqueScript`: the bytes of the script when they are not a list of opcodes, in which case `script` is empty. `viewTaprootWitness` now reads a script path spend whatever its script bytes are. It used to return `Nothing` for a Tapscript with an `OP_SUCCESSx` opcode followed by bytes that do not decode, and for a leaf of another version, although such spends can be valid. `verifyScriptPathData` takes the leaf hash on the script bytes of the witness, and is `False` for a proof of more than 128 hashes.+- `encodeTaprootWitness` returns `Either String WitnessStack`. It fails for an annex that is empty or does not start with `0x50`, for a proof of more than 128 hashes, and for a leaf version that is odd or is `0x50`. It used to write a witness that reads back as another spend, or that every node refuses.+- `taprootSigHash` fails for an annex that is empty or does not start with `0x50`. No node takes such an item for an annex, so no node computes that digest.+- `data/bip341.json` is the file of the BIPs repository, whose key is `fullySignedTx`.+- `Signer` has a fourth field, `requirePreviousTx`.+- `keySigner` and `xPrvSigner` refuse a SegWit version 0 coin without its previous transaction. Attach the previous transaction, or set `requirePreviousTx = False` to trust the PSBT writer.+- `psbtTransaction` refuses a PSBT with an input that is not final. Use `psbtPartialTransaction` for the old behaviour.+- `decodePsbt` refuses what Bitcoin Core refuses: a key type that is not a compact size in its shortest form, a public key that is not on the curve, a partial signature that is not in the encoding of BIP 66 with a defined sighash byte, a previous transaction that the input does not spend, and a script tree that is not whole.+- `signPsbt` and `finalizePsbt` leave alone an input whose redeem or witness script is not a standard template, instead of failing the whole PSBT.+- `signPsbt` signs only for keys that the signed script names. It adds no signature to a script-hash coin whose script is missing, and signs a Taproot leaf only when its control block commits it to the coin.+- `signPsbt` and `finalizePsbt` refuse an uncompressed key on a SegWit version 0 coin.+- `finalizePsbt` verifies only the signatures of keys the script names, against one digest per input.+- `finalizePsbt` refuses a signature that is not `SIGHASH_ALL` (`SIGHASH_DEFAULT` for Taproot) when the input names no sighash type, and leaves alone a legacy coin known from its witness output only.+- `addInput` and `addOutput` check the map they are given.+- A PSBT error names at most 40 bytes of a key.++### Fixed++- The documentation of the hardened markers, of the depth limit and of `decodeSecKey` no longer describes old behaviour of Bitcoin Core and of the secp256k1 binding. The documentation of `PrivateKey` and `XPrvKey` says that the `Show` instance of the `SecKey` inside them prints the secret.+- The documentation of `TxSignature`, `decodeTxSig` and `decodeInput` lists the rules they apply: strict DER, a low S and a defined hash type, the three signature rules of the relay policy of Bitcoin Core. A signature that only consensus accepts does not decode, so an input of an old block can fail to decode. The documentation also says that a Schnorr signature of Bitcoin Cash does not decode.+- The documentation of `decodeOutput` says that a multisig whose keys are not direct pushes, and a data carrier whose pushes are not the shortest, come back from `encodeOutput` as other bytes, and lists the scripts that the `Solver` of Bitcoin Core classifies and this function does not: a key in hybrid form or off the curve, and `OP_RETURN OP_RESERVED`.+- The documentation of `txSigHash` names `SerializeScriptCode` of Bitcoin Core for the removal of `OP_CODESEPARATOR`, and says that the pushes of the signature are not removed from the script, which `FindAndDelete` does in Bitcoin Core. The documentation of `txSigHashForkId` lists what is not supported on Bitcoin Cash: `SIGHASH_UTXOS`, coins that hold tokens, and Schnorr signatures.+- The documentation of `Address` says that a token-aware CashAddr and its plain form are different values that pay the same script, and that a value built with a constructor or read from the binary form is not checked against a network. The documentation of `textToAddr` says that white space around an address is refused and that some CashAddr kinds decode without being payable. The documentation of `addressToOutput` says that it does not know the network.+- The documentation of `maxMultisigKeys` says that 20 keys fit a bare and a pay-to-witness-script-hash multisig only.+- The documentation of `verifyStdTx` and `verifyStdInput` says what they check and what they do not: they are not a script interpreter, they never answer `True` for an input of the kinds they know that no block can contain, and they answer `False` for some inputs that a block may hold but nodes do not relay.+- The documentation of `SigInput` says that the value of a legacy coin is not committed to by its signature, so that a signer cannot check it without the previous transaction.+- The documentation of `guessTxSize` and `txVirtualSize` says that they do not count signature operations, which Bitcoin Core charges for in a transaction that is dense in them.+- The documentation of `InputKind` gives the ranges of a multisig kind, and that of `buildTx` and `buildAddrTx` says what they do not check.+- `leafHash`, and with it `mastCommitment`, `taprootOutputKey`, `taprootScriptOutput`, `getMerkleProofs` and `verifyScriptPathData`, write the script length of a Taproot leaf as a CompactSize, as BIP 341 defines the tapleaf hash and as `ComputeTapleafHash` does in Bitcoin Core. They wrote a base-128 varint, which is the same byte below 128 only. A leaf script of 128 bytes or more, such as a `multi_a` of four keys, therefore got a leaf hash, an output key and an address that no other software derives, and that leaf could not be spent. **The address of every script tree with such a leaf changes.** Trees whose leaf scripts are all shorter than 128 bytes, and outputs without a tree, are not affected. Coins at an address that version 1.5.1 or earlier made from such a tree can still move by the key path, or by the script path of a leaf shorter than 128 bytes. To rebuild the old output, put the old hash of each long leaf in the tree as a `MASTCommitment`. The old hash is the tagged hash `TapLeaf` of the leaf version, the script length as `Data.Bytes.VarInt.VarInt` of the bytes package, and the script bytes. `taprootOutputKey` and `taprootOutputSecKey` then give the old output key and the secret key of its key path, and `getMerkleProofs` gives the proofs of the short leaves. The long leaves of such an output can never be spent. The test "An output made by versions up to 1.5.1 with a leaf of 128 bytes or more" in `TaprootSpec.hs` has the code.+- `getMerkleProofs` lists the hashes of a proof from the leaf up to the root for a leaf at any depth, as BIP 341 and Bitcoin Core do. From depth 3 the order was wrong, and every node refused the control block built from it. It also hashes each subtree once instead of once per level.+- The JSON form of `XOnlyPubKey` reads the hex of 32 bytes only. It used to read the first 32 bytes of a longer value, so a 33-byte compressed key was taken for another key.+- `tapTreeLeaves` returns `Nothing` for a tree with a leaf deeper than 128, and no longer wraps the depth at 256.+- `xPrvSigner` no longer throws for a derivation path of more than 255 steps. The input stays unsigned.+- `signPsbt` signs a P2SH-P2WPKH coin by the hash of its key, without a derivation record.+- `signPsbt` and `finalizePsbt` use `SIGHASH_ALL` with the fork id flag on Bitcoin Cash when an input names no sighash type.+- `signPsbt` signs a Taproot key path when the record of the internal key lists a leaf.+- The PSBT scripts under `scripts/` run again, against Bitcoin Core 32.1.+ ## [1.5.1] - 2026-11-04 ### Fixed@@ -33,7 +165,7 @@ - `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.+- Derivation paths accept `h` and `H` as hardened markers, so `m/84h/0h/0h` parses. Bitcoin Core and BIP 380 write `'` or `h`, and BIP 32 writes a subscript H. - 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.
README.md view
@@ -1,21 +1,838 @@ # Haskoin Core -Haskoin Core is a library of Bitcoin and Bitcoin Cash functions written in Haskell featuring:+[](https://hackage.haskell.org/package/haskoin-core)+[](https://github.com/jprupp/haskoin-core/blob/master/LICENSE) -- Hashing functions (SHA-256, RIPEMD-160)-- Base58 support-- CashAddr support-- Bech32 suport-- BIP32 extended key derivation and parsing (m/1'/2/3)-- BIP39 mnemonic keys-- ECDSA secp256k1 cryptographic primitives-- Script parsing-- Building and signing of standard transactions (regular, multisig, p2sh, segwit)-- Parsing and manipulation of all Bitcoin and Bitcoin Cash protocol messages-- Bloom filters and partial merkle trees (used in SPV wallets)-- Comprehensive test suite+Haskoin Core is a Bitcoin and Bitcoin Cash library for Haskell. It covers keys,+addresses, scripts, transactions, PSBTs, block headers and the peer-to-peer+message format. -## Contributing+It does no I/O of its own. It never opens a socket or writes a file. You can use+it in a wallet, a block explorer or a node, and bring your own networking and+storage. -All code is formatted with [Ormolu](https://github.com/tweag/ormolu).-Convenience formatting script available at [scripts/format](scripts/format)+- [API documentation](https://hackage.haskell.org/package/haskoin-core)+- [Changelog](https://github.com/jprupp/haskoin-core/blob/master/CHANGELOG.md)+- [Report a bug](https://github.com/jprupp/haskoin-core/issues)++## Contents++- [Features](#features)+- [Installation](#installation)+- [Quick start](#quick-start)+- [Concepts](#concepts)+- [Usage guide](#usage-guide)+- [Module overview](#module-overview)+- [Security notes](#security-notes)+- [Development](#development)+- [Related projects](#related-projects)+- [License](#license)++## Features++| Area | What you get |+| ------------ | -------------------------------------------------------------------------------------------------------------------- |+| Networks | Bitcoin and Bitcoin Cash: mainnet, testnet3, testnet4 and regtest |+| Keys | secp256k1 keys, WIF, BIP-32 extended keys and paths, BIP-39 mnemonics, SLIP-39 Shamir shares |+| Signatures | Deterministic ECDSA (RFC 6979) and BIP-340 Schnorr |+| Addresses | Base58, Bech32, Bech32m and CashAddr, including token-aware CashAddr types |+| Scripts | Script parsing and serialization, standard input and output templates, BIP-67 sorted multisig |+| Transactions | Building, signing and verifying P2PK, P2PKH, P2SH, multisig, P2WPKH, P2WSH, nested SegWit and Taproot key path spends |+| Wallet tools | Coin selection, and weight, size and fee estimates |+| PSBT | Versions 0 and 2, with the Taproot fields (BIP-174, BIP-370, BIP-371) |+| Taproot | Script trees, output keys, witnesses, control blocks and signature hashes (BIP-341, BIP-342) |+| Blocks | Header validation, proof of work, the difficulty rules of both chains, Merkle trees and an in-memory header chain |+| Peer-to-peer | Message encoding, including `addrv2`, compact blocks and compact filter messages, and BIP-37 Bloom filters |+| Testing | QuickCheck generators for the library's types |++### What is not included++- **Peer connections and chain sync.** Use+ [haskoin-node](https://github.com/jprupp/haskoin-node).+- **Address and transaction indexing.** Use+ [haskoin-store](https://github.com/jprupp/haskoin-store).+- **A script interpreter.** `verifyStdTx` recognises the standard templates. It+ is not a consensus check.+- **Signet, output descriptors, Miniscript and MuSig2.**+- **Schnorr transaction signatures, `SIGHASH_UTXOS` and token outputs of Bitcoin+ Cash.** An input or an output that uses them does not decode or verify.+- **BIP-324 encrypted transport.**+- **BIP-39 word lists other than English.**++## Installation++### System library++Haskoin Core needs [libsecp256k1](https://github.com/bitcoin-core/secp256k1)+0.2.0 or later, and `pkg-config` to find it.++```sh+# Debian and Ubuntu+sudo apt-get install libsecp256k1-dev pkg-config++# macOS with Homebrew+brew install secp256k1 pkg-config++# Nix+nix-shell -p secp256k1 pkg-config+```++Check what you have with `pkg-config --modversion libsecp256k1`. Debian 11 and+other older systems ship a version below 0.2.0. On those, build the library from+source and enable its recovery module with `./configure --enable-module-recovery`.++### Compiler++The library is tested with GHC 9.10 and 9.12. It cannot build below GHC 9.2,+because it uses `OverloadedRecordDot`.++### With Cabal++Add the packages to your `.cabal` file:++```cabal+build-depends:+ base+ , bytes+ , haskoin-core+ , text+```++You need `bytes` to serialize values with `runPutS` and `serialize`.++Run `cabal update` first. Haskoin Core needs a recent secp256k1-haskell, and a+stale package index makes the build fail with a dependency error.++### With Stack++Haskoin Core is not in Stackage, so add it to `stack.yaml` as an extra+dependency. Use the [latest version from Hackage](https://hackage.haskell.org/package/haskoin-core).++```yaml+resolver: nightly-2026-10-28+extra-deps:+ - haskoin-core-1.6.0+ - base16-2.0+ - secp256k1-haskell-2.0.0+```++## Quick start++This program turns a mnemonic into the first receiving address of a BIP-84+wallet.++```haskell+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Data.Text (Text)+import Haskoin++-- This is the public BIP-39 test mnemonic. Never send coins to it.+mnemonic :: Text+mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"++main :: IO ()+main = withContext $ \ctx -> do+ seed <- either fail pure (mnemonicToSeed "" mnemonic)+ let master = makeXPrvKey seed+ -- Hardened steps need the private key.+ account = derivePath ctx ("m/84'/0'/0'" :: HardPath) master+ accountPub = deriveXPubKey ctx account+ -- The remaining steps only need the public key.+ receiving = derivePubPath ctx ("m/0" :: SoftPath) accountPub+ (address, _publicKey) = deriveWitnessAddr ctx receiving 0+ print (addrToText btc address)+ -- => Just "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"+```++The address is the one that [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki)+publishes for this mnemonic.++## Concepts++A few conventions apply across the whole library. Reading this section first+will save you time.++### The context++Most cryptographic functions take a `Ctx`, a secp256k1 context. Create one when+your program starts, with `withContext` or `createContext`. Then pass it around.+You can share it between threads.++### Networks++A `Network` is a record of the parameters of one chain. Functions that depend on+the chain take one as an argument. Functions that need both take the `Network`+first and the `Ctx` second.++| Value | Chain | Name for `netByName` |+| ------------ | --------------------- | -------------------- |+| `btc` | Bitcoin mainnet | `btc` |+| `btcTest` | Bitcoin testnet3 | `btctest` |+| `btcTest4` | Bitcoin testnet4 | `btctest4` |+| `btcRegTest` | Bitcoin regtest | `btcreg` |+| `bch` | Bitcoin Cash mainnet | `bch` |+| `bchTest` | Bitcoin Cash testnet3 | `bchtest` |+| `bchTest4` | Bitcoin Cash testnet4 | `bchtest4` |+| `bchRegTest` | Bitcoin Cash regtest | `bchreg` |++### Record fields++The library is built with `NoFieldSelectors`, so record fields are not functions.+Turn on `OverloadedRecordDot` in your modules and read fields with a dot.++```haskell+print btc.defaultPort+-- => 8333+print (map (.value) btc.genesisTx.outputs)+-- => [5000000000]+```++Many records share field names. `Tx` and `Psbt` both have `inputs`, for example.+To update such a field, import the module qualified and name the field through+it, as the [PSBT example](#psbt) does.++### Errors++Functions that can fail return `Maybe` or `Either String`. The examples below+turn those into `IO` failures with `either fail pure`.++Hashes, paths and public keys have `IsString` instances, so you can write them as+literals. Those instances call `error` on bad input. For input you do not control,+use `hexToTxHash`, `hexToBlockHash`, `parsePath` and `textToAddr`.++### Amounts++Amounts are satoshi, as `Word64`.++### Serialization++Most types have `Serial` instances, from the+[bytes](https://hackage.haskell.org/package/bytes) package, and also `Binary` and+`Serialize` instances. Some types need a context or a network to encode, so they+have `Marshal` or `MarshalJSON` instances instead.++| Task | Encode | Decode |+| ------------------- | -------------------------- | ---------------------------- |+| Binary | `runPutS (serialize x)` | `runGetS deserialize bytes` |+| Binary with context | `marshal ctx x` | `unmarshal ctx bytes` |+| Hexadecimal | `encodeHex bytes` | `decodeHex text` |+| JSON | `toJSON x` | `parseJSON value` |+| JSON with context | `marshalJSON ctx x` | `unmarshalJSON ctx json` |++The first argument depends on the type. For example, it is a `Ctx` for+`PublicKey`, a `Network` for `XPrvKey` and a `(Network, Ctx)` pair for `XPubKey`.++`unmarshal` fails when bytes are left over after the value, and `decodeTx` does+the same for a transaction. `runGetS deserialize` reads a value from the start of+the bytes and ignores the rest.++### Hash byte order++`TxHash` and `BlockHash` show as hexadecimal in reversed byte order. This is the+form that Bitcoin Core and block explorers display, so you can paste those values+straight in. The serialized bytes keep the internal order.++## Usage guide++The examples below are fragments of `main` in this program. Paste one in place of+the comment to run it. If an example starts with `import` lines, add them to the+top of the file. A `-- =>` comment shows what the line above it prints.++```haskell+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (serialize)+import Haskoin++main :: IO ()+main = withContext $ \ctx -> do+ -- This is the public BIP-39 test mnemonic. Never send coins to it.+ seed <-+ either fail pure $+ mnemonicToSeed "" "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"+ let master = makeXPrvKey seed+ -- Paste an example here.+ pure ()+```++### Mnemonics++Generate entropy with a cryptographic source. This example uses the+[entropy](https://hackage.haskell.org/package/entropy) package.++```haskell+import qualified Data.ByteString as B+import System.Entropy (getEntropy)++-- 16 bytes of entropy give 12 words. 32 bytes give 24 words.+entropy <- getEntropy 16+words12 <- either fail pure (toMnemonic entropy)++-- The passphrase is optional. Use "" for none.+newSeed <- either fail pure (mnemonicToSeed "my passphrase" words12)+print (B.length newSeed)+-- => 64+```++`mnemonicToSeed` checks the words and the checksum. `fromMnemonic` gives the+entropy back.++### Keys and signatures++```haskell+-- A secret key is 32 bytes. decodeSecKey refuses zero and values at or above+-- the curve order.+bytes <- maybe (fail "bad hex") pure (decodeHex "0000000000000000000000000000000000000000000000000000000000000001")+secret <- maybe (fail "bad key") pure (decodeSecKey bytes)++-- True asks for the compressed form of the public key.+let private = wrapSecKey True secret+ public = derivePublicKey ctx private++print private+-- => PrivateKey {key = <redacted>, compress = True}+print (toWif btc private)+-- => "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"+print (encodeHex (marshal ctx public))+-- => "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"+print (addrToText btc (pubKeyAddr ctx public))+-- => Just "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"++-- Sign and verify a hash.+let digest = doubleSHA256 bytes+ signature = signHash ctx secret digest+print (verifyHashSig ctx digest signature public.point)+-- => True+```++`fromWif btc text` reads a WIF key back. `Haskoin` also re-exports the+[secp256k1-haskell](https://hackage.haskell.org/package/secp256k1-haskell) API,+which has the Schnorr functions such as `signSchnorr` and `verifySchnorr`.++### Extended keys++Paths have three types. A `HardPath` has only hardened steps. A `SoftPath` has+only normal steps, which a public key can derive. A `DerivPath` can mix both.+Hardened steps can be written `'`, `h` or `H`.++```haskell+let account = derivePath ctx ("m/84'/0'/0'" :: HardPath) master+ accountPub = deriveXPubKey ctx account++print (xPubExport btc ctx accountPub)+-- => "xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V"++-- The account's public key derives all its receiving addresses.+let receiving = derivePubPath ctx ("m/0" :: SoftPath) accountPub+ addresses = [address | (address, _, _) <- deriveWitnessAddrs ctx receiving 0]+print (map (addrToText btc) (take 2 addresses))+-- => [Just "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",Just "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"]+```++Pick the address function that matches the wallet standard:++| Standard | Address type | Path | One address | Endless list |+| -------- | ------------------- | ------------- | ------------------------- | -------------------------- |+| BIP-44 | P2PKH | `m/44'/0'/0'` | `deriveAddr` | `deriveAddrs` |+| BIP-49 | P2WPKH nested in P2SH | `m/49'/0'/0'` | `deriveCompatWitnessAddr` | `deriveCompatWitnessAddrs` |+| BIP-84 | P2WPKH | `m/84'/0'/0'` | `deriveWitnessAddr` | `deriveWitnessAddrs` |+| BIP-86 | Taproot | `m/86'/0'/0'` | See [Taproot](#taproot) | |++`xPrvExport` and `xPubExport` write the standard `xprv` and `xpub` forms.+`xPrvImport` and `xPubImport` read them back. The `ypub` and `zpub` prefixes are+not supported.++`parsePath` reads a path at run time. It returns a `ParsedPath`, which+`applyPath` applies to a private or a public key.++### Addresses++```haskell+let text = "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"++print (textToAddr btc text)+-- => Just (WitnessPubKeyAddress {hash160 = "c0cebcd6c3d3ca8c75dc5ec62ebe55330ef910e2"})++-- An address of another network is refused.+print (textToAddr bch text)+-- => Nothing++-- The output script an address pays to.+print (encodeHex <$> (addressToScriptBS ctx =<< textToAddr btc text))+-- => Just "0014c0cebcd6c3d3ca8c75dc5ec62ebe55330ef910e2"+```++`textToAddr` and `addrToText` handle every address format of a network.+`addrToBase58`, `addrToBech32` and `addrToCash` each write one chosen format.++### Scripts++`ScriptOutput` is a standard output script. Its constructors are `PayPK`,+`PayPKHash`, `PayMulSig`, `PayScriptHash`, `PayWitnessPKHash`,+`PayWitnessScriptHash`, `PayWitness`, `PayScript32Hash` and `DataCarrier`.++```haskell+let keyA = wrapPubKey True "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8"+ keyB = wrapPubKey True "02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f"+ -- BIP-67 sorts the keys, so that all signers build the same script.+ multisig = sortMulSig ctx (PayMulSig [keyA, keyB] 2)++-- The address can fail, because a P2SH redeem script holds at most 520 bytes.+print (addrToText btc <$> payToScriptAddress ctx multisig)+-- => Right (Just "39bgKC7RFbpoCRbtD5KEdkYKtNyhpsNa3Z")++-- OP_RETURN is 6a, then the push of 5 bytes, then "hello".+print (encodeHex (marshal ctx (DataCarrier ["hello"])))+-- => "6a0568656c6c6f"++-- Read the same bytes back.+script <- maybe (fail "bad hex") pure (decodeHex "6a0568656c6c6f")+print (unmarshal ctx script :: Either String ScriptOutput)+-- => Right (DataCarrier {pushes = ["hello"]})+```++Decoding a `ScriptOutput` fails for a script that matches no template. A `Script`+holds any script, as a list of `ScriptOp` values, so use that type for the others.+`encodeOutput` and `decodeOutput` convert between `ScriptOutput` and `Script`.++`payToWitnessScriptAddress` and `payToNestedScriptAddress` give the P2WSH and+P2SH-P2WSH addresses of a script.++The three functions return `Either`. A P2SH redeem script holds at most 520+bytes, which is a multisig of 15 compressed keys. Coins sent to a longer script+could never be spent, so `payToScriptAddress` refuses it. A P2WSH script holds up+to 10,000 bytes, so a multisig of 16 to 20 keys needs P2WSH.++### Building and signing a transaction++```haskell+-- The key at m/84'/0'/0'/0/0 and its address.+let key = derivePath ctx ("m/84'/0'/0'/0/0" :: DerivPath) master+ address = xPubWitnessAddr ctx (deriveXPubKey ctx key)++-- The coin to spend, and the transaction that paid it. Both are made up.+-- The coin holds 100,000 satoshi.+coinScript <- maybe (fail "no output script") pure (addressToOutput address)+let amount = 100000+ fundingInput = TxIn (OutPoint "1111111111111111111111111111111111111111111111111111111111111111" 0) "" 0xffffffff+ funding = Tx 2 [fundingInput] [TxOut amount (marshal ctx coinScript)] [] 0+ coin = OutPoint (txHash funding) 0++-- Pay 99,000 satoshi. The 1,000 satoshi left over are the fee.+unsigned <-+ either fail pure $+ buildAddrTx btc ctx [coin] [("bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g", 99000)]++-- SigInput describes the coin. The last argument is the redeem script.+signed <-+ either fail pure $+ signTx btc ctx unsigned [SigInput coinScript amount coin sigHashAll Nothing] [key.key]++print (verifyStdTx btc ctx signed [(coinScript, amount, coin)])+-- => True+print (txVirtualSize signed)+-- => 110+print (txHash signed)+-- => "9a079e6b7939909415b7b027a19e093bc539dac5a2ed50df8beb0942ec821848"++-- This hexadecimal form is what you broadcast.+print (encodeHex (runPutS (serialize signed)))+-- => "01000000000101..."+```++`buildAddrTx` adds no change output. Whatever the outputs do not spend goes to the+miners as a fee, so list your change output along with the payments.++Fill in `SigInput` and pick the signing function by the type of the coin:++| Coin | `script` | `redeem` | Function |+| ---------------- | ---------------------- | ------------------------ | --------------------- |+| P2PKH | `PayPKHash` | `Nothing` | `signTx` |+| P2SH multisig | `PayScriptHash` | `Just (PayMulSig ks m)` | `signTx` |+| P2WPKH | `PayWitnessPKHash` | `Nothing` | `signTx` |+| P2WSH | `PayWitnessScriptHash` | `Just script` | `signTx` |+| P2SH-P2WPKH | `PayWitnessPKHash` | `Nothing` | `signNestedWitnessTx` |+| P2SH-P2WSH | `PayWitnessScriptHash` | `Just script` | `signNestedWitnessTx` |+| Taproot key path | `PayWitness 1 key` | `Nothing` | `signTx` |++For multisig, each signer can call `signTx` with their own keys. `mergeTxs`+combines the results for legacy inputs. For SegWit multisig, use a [PSBT](#psbt).++### Coin selection and fees++Make your coin type an instance of `Coin`. The library then picks coins and+estimates the fee from the kinds of the inputs and outputs. This example is a+complete program.++```haskell+{-# LANGUAGE OverloadedRecordDot #-}++module Main (main) where++import Data.Word (Word64)+import Haskoin++newtype Utxo = Utxo {amount :: Word64}++instance Coin Utxo where+ coinValue utxo = utxo.amount+ coinInputKind _ = InputWitnessPKHash++main :: IO ()+main = do+ let outputs = [OutputWitnessPKHash, OutputWitnessPKHash]+ coins = [Utxo 50000, Utxo 120000, Utxo 300000]+ target = 150000+ feeRate = 5++ -- The size is in virtual bytes, and the fee rate in satoshi per virtual byte.+ print (guessTxSize [InputWitnessPKHash] outputs)+ -- => 141+ print (guessTxFee feeRate [InputWitnessPKHash] outputs)+ -- => Right 705++ -- The result is the chosen coins and the change to send back to yourself.+ -- True keeps searching the rest of the list for a smaller solution.+ case chooseCoins target feeRate outputs True coins of+ Left err -> putStrLn err+ Right (chosen, change) -> print (map (.amount) chosen, change)+ -- => ([120000,50000],18955)+```++The change here is 170,000 - 150,000 - 1,045, where 1,045 is the fee for two+inputs and two outputs.++`chooseCoins` takes the coins in the order of the list, so sort them the way you+prefer first. The output list must include the change output.++The change can be smaller than the `dustThreshold` of its output kind, down to+one satoshi. Nodes do not relay such an output, so leave that change to the fee.+`chooseCoins` refuses a selection above `maxStandardTxWeight`, which nodes do not+relay either.++The estimates are upper bounds, because they count 72 bytes per signature. The+kinds include `InputPKHash`, `InputWitnessPKHash` and `InputTaproot`. See+`InputKind` and `OutputKind` in `Haskoin.Transaction.Builder` for all of them.++### PSBT++Use a PSBT when several parties or devices sign a transaction. This example+continues the one in [Building and signing](#building-and-signing-a-transaction).++```haskell+-- Continues the example above.+import qualified Haskoin.Transaction.Psbt as Psbt++-- Creator: start from the unsigned transaction.+let created = psbtOf unsigned++-- Updater: attach the transaction that paid each coin. The signer checks the+-- amount of the coin against it.+ updated = created {Psbt.inputs = map (insertNonWitnessUtxo funding) created.inputs}++-- This base64 text is what you pass to the signers.+let shared = encodePsbtBase64 updated++-- Signer: read the text, and sign every input the keys can sign.+received <- either fail pure (decodePsbtBase64 shared)+signedPsbt <- either fail pure (signPsbt btc ctx (keySigner ctx key.key) received)++-- Finalizer and extractor: check the signatures and build the transaction.+finalized <- either fail pure (finalizePsbt btc ctx signedPsbt)+extracted <- either fail pure (psbtTransaction finalized)++-- RFC 6979 signatures are deterministic, so this is the same transaction.+print (extracted == signed)+-- => True+```++Some other useful functions:++- `xPrvSigner` signs from an extended private key, following the derivation+ records of the inputs.+- `combinePsbt` merges the copies that different signers return.+- `insertWitnessUtxo` attaches a coin without the transaction that paid it. That+ is enough for a Taproot coin. For a SegWit version 0 coin, the amount is then a+ claim that the signature does not cover for the other inputs, and a signer can+ be led to burn coins as fee (CVE-2020-14199). `keySigner` and `xPrvSigner`+ therefore refuse such a coin. Set `requirePreviousTx = False` on the `Signer`+ to accept it.+- `psbtFee` and `inputCoins` give the fee and the coins as the signer checks+ them. Show those to the user, not amounts read from the fields yourself.+- `psbtTransaction` refuses a PSBT whose inputs are not all final.+ `psbtPartialTransaction` gives the transaction of a PSBT that is still being+ signed.+- `insertInputRedeemScript` and `insertInputWitnessScript` attach the scripts of+ P2SH and P2WSH coins.+- `withVersion Version2` converts a PSBT to version 2. `addInput` and `addOutput`+ then extend it.++### Taproot++```haskell+let key = derivePath ctx ("m/86'/0'/0'/0/0" :: DerivPath) master+ internalKey = (deriveXPubKey ctx key).key+ -- Nothing means that the output has no script tree.+ output = taprootScriptOutput ctx (TaprootOutput internalKey Nothing)++print (addrToText btc =<< outputAddress btc ctx output)+-- => Just "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"++-- Spend a made-up coin of 100,000 satoshi by its key path.+let coin = OutPoint "1111111111111111111111111111111111111111111111111111111111111111" 0+ unsigned = buildTx ctx [coin] [(output, 99000)]++signed <-+ either fail pure $+ signTx btc ctx unsigned [SigInput output 100000 coin sigHashDefault Nothing] [key.key]++print (verifyStdTx btc ctx signed [(output, 100000, coin)])+-- => True+```++The signature commits to every coin the transaction spends. `signTx` therefore+needs a `SigInput` for every input of a Taproot transaction.++To commit to scripts, pass a `MAST` in place of `Nothing`. `signTx` only signs key+path spends. `signPsbt` also signs a script path when the leaf is a single key+check, `<key> OP_CHECKSIG`. For other scripts, build the witness yourself with+`taprootSigHash` and `encodeTaprootWitness`.++`checkTapTree` tells whether every leaf of a tree can be spent. A leaf can be at+most 128 levels deep, and its version must be one that BIP 341 allows.++### SLIP-39 secret sharing++[SLIP-39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) splits a+secret into mnemonic shares, and a threshold of shares rebuilds it. It is+available from version 1.6.0. The `Haskoin` module does not re-export it, so+import it yourself.++```haskell+import qualified Haskoin.Crypto.Keys.Slip39 as Slip39++-- One group of 3 members, of which 2 rebuild the secret.+let scheme = Slip39.Scheme 1 [(2, 3)]+ -- A secret has at least 16 bytes, and an even number of them.+ -- Use random bytes in practice.+ secret = "0123456789abcdef"++-- True makes the shares extendable. 1 sets the cost of the encryption.+groups <- either fail pure =<< Slip39.generateMnemonics scheme True 1 "passphrase" secret+let shares = concat groups++print (length shares)+-- => 3+print (Slip39.combineMnemonics "passphrase" (take 2 shares))+-- => Right "0123456789abcdef"+print (Slip39.combineMnemonics "passphrase" (take 1 shares))+-- => Left "Group 0 has 1 share, but its member threshold is 2"+```++A wrong passphrase is not an error. It returns a different secret, as the+specification requires. The passphrase must be printable ASCII, which the+specification requires too. A SLIP-39 share is not a BIP-39 mnemonic, and one+cannot be converted into the other.++`Scheme` can also describe two levels. For example, `Scheme 2 [(1, 1), (2, 3),+(3, 5)]` needs two of the three groups, each with its own member threshold.++### Bitcoin Cash++Pass `bch` wherever the examples above pass `btc`. A few things differ.++```haskell+-- The BIP-44 coin type of Bitcoin Cash is 145.+let key = derivePath ctx ("m/44'/145'/0'/0/0" :: DerivPath) master+ address = xPubAddr ctx (deriveXPubKey ctx key)++-- addrToText writes CashAddr on Bitcoin Cash.+print (addrToText bch address)+-- => Just "bitcoincash:qqyx49mu0kkn9ftfj6hje6g2wfer34yfnq5tahq3q6"+print (addrToBase58 bch address)+-- => Just "1mW6fDEMjKrDHvLvoEsaeLxSCzZBf3Bfg"++-- textToAddr reads both forms.+print ((== Just address) (textToAddr bch "1mW6fDEMjKrDHvLvoEsaeLxSCzZBf3Bfg"))+-- => True++-- SegWit addresses do not exist on Bitcoin Cash.+print (addrToText bch (xPubWitnessAddr ctx (deriveXPubKey ctx key)))+-- => Nothing+```++`signTx` sets the fork id flag of the signature hash when the network needs it.+Pass `sigHashAll` as usual.++### Blocks and headers++```haskell+print (headerHash btc.genesisHeader)+-- => "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"+print (isValidPOW btc btc.genesisHeader)+-- => True++-- 3.125 BTC since the halving at this height.+print (computeSubsidy btc 840000)+-- => 312500000+```++`connectBlocks` validates headers and adds them to a chain. This covers proof of+work, difficulty, timestamps, versions and checkpoints. It runs in any monad with+a `BlockHeaders` instance. `StateT HeaderMemory` is the in-memory one. Write your+own instance to keep headers in a database.++Once the best block has the `minimumChainWork` of the network, a fork with less+work is refused. Below that work nothing bounds the side chains a peer can send,+so a node that is still syncing should not keep headers from a peer it does not+trust.++This example needs the [mtl](https://hackage.haskell.org/package/mtl) package.++```haskell+import Control.Monad.State.Strict (runStateT)++-- Mine three regtest headers on top of the genesis block.+let net = btcRegTest+ headers = appendBlocks net 7 net.genesisHeader 3+ now = net.genesisHeader.timestamp + 3600++(result, chain) <- runStateT (connectBlocks net now headers) (initialChain net)+nodes <- either fail pure result++-- The nodes come back with the highest one first.+print (map (.height) nodes)+-- => [3,2,1]+print chain.best.height+-- => 3+```++`buildMerkleRoot` and `buildPartialMerkle` compute Merkle trees.+`extractMatches` checks a partial tree from a `merkleblock` message against its+Merkle root. The header and the number of transactions are claims of the sender,+which the caller must check.++### Peer-to-peer messages++The library encodes and decodes messages. Moving the bytes is up to you.++```haskell+import Data.Bytes.Get (runGetS)++let bytes = runPutS (putMessage btc MVerAck)++print (encodeHex bytes)+-- => "f9beb4d976657261636b000000000000000000005df6e0e2"+print (runGetS (getMessage btc) bytes)+-- => Right MVerAck+```++`getMessage` checks the network magic and the checksum, and bounds the payload by+the `maxPayloadSize` of its command. Read the 24-byte `MessageHeader` first to+learn how many more bytes to take from the socket, and refuse a length above+`maxPayloadSize net command` before you read them.++`Message` has a constructor for each message type. `bloomCreate`, `bloomInsert`+and `bloomContains` build BIP-37 filters for `filterload`.++## Module overview++`import Haskoin` brings in everything below except the two modules marked as+separate. It exports many short names, so you may prefer to import only the+modules you need, or to import it qualified.++| Module | Contents |+| ----------------------------- | -------------------------------------------------------------------- |+| `Haskoin.Address` | Address types and Base58, Bech32 and CashAddr encoding |+| `Haskoin.Block` | Block types, header chain, validation, difficulty and Merkle trees |+| `Haskoin.Crypto` | Hashes, keys, signatures and the `Crypto.Secp256k1` API |+| `Haskoin.Crypto.Keys.Slip39` | SLIP-39 secret sharing (separate import) |+| `Haskoin.Network` | Network constants, P2P messages, Bloom filters |+| `Haskoin.Script` | Scripts, standard templates and signature hashes |+| `Haskoin.Transaction` | Transaction types, builder, signing, SegWit, Taproot and PSBT |+| `Haskoin.Util` | Hexadecimal helpers, `Marshal` and small utilities |+| `Haskoin.Util.Arbitrary` | QuickCheck generators such as `arbitraryTx` (separate import) |++## Security notes++Bugs in code that handles keys can lose money. Try your program on regtest or a+test network before you use it on mainnet.++- **Randomness.** Create keys and mnemonics with a cryptographic source, such as+ `getEntropy` from the entropy package. Never use `System.Random`.+- **Secrets in logs.** `PrivateKey` and `XPrvKey` show as `<redacted>` and have no+ `Read` instance. `toWif` and `xPrvExport` write a key out on purpose. A bare+ `SecKey`, such as the `key` field of a `PrivateKey`, still shows its bytes. The+ errors of `fromMnemonic` and `mnemonicToSeed` name the position of a bad word,+ never the word.+- **Secrets in memory.** Keys live in the GHC heap. The library cannot wipe them,+ and the garbage collector can copy them.+- **Review before you sign.** `signPsbt` checks that a PSBT is sound, but not that+ it pays who you expect. Show the outputs and `psbtFee` to the user first. By+ default the signer wants the previous transaction of every SegWit version 0+ coin, because an amount in a witness output alone is a claim.+- **Signature hash types.** By default `signPsbt` signs only `SIGHASH_ALL` and+ `SIGHASH_DEFAULT`, because the other types leave part of the transaction+ unsigned. To accept others, set `acceptedSigHashes` on the `Signer`.+- **Verification.** `verifyStdTx` checks the signatures of the standard templates+ by the rules that nodes relay by, and that outputs do not exceed inputs. It is+ not a script interpreter and does not apply every consensus rule.+- **Untrusted text.** The cost of `decodeBase58` and `decodeBase58Check` grows+ with the square of the input length. Limit the length of input you pass to them+ directly. The address and key decoders of the library already do.++## Development++```sh+git clone https://github.com/jprupp/haskoin-core.git+cd haskoin-core++stack build # build the library+stack test # run all the tests+stack test --test-arguments='--match "Slip39"' # run the tests of one module+stack haddock --no-haddock-deps # build the API documentation+```++`cabal build` and `cabal test` work too. On NixOS, or with `stack --nix`, the+`nix` section of `stack.yaml` provides libsecp256k1.++`package.yaml` is the source of the package description. Change it rather than+`haskoin-core.cabal`, which [hpack](https://github.com/sol/hpack) generates. Stack+regenerates the file whenever it builds.++The tests are in `test/` and use [hspec](https://hspec.github.io/). The test+vectors from the BIPs, Bitcoin Core and other implementations are in `data/`.++### Contributing++Pull requests are welcome.++- Format the code with [Ormolu](https://github.com/tweag/ormolu). The script+ [scripts/format](https://github.com/jprupp/haskoin-core/blob/master/scripts/format)+ does this for all the sources.+- Add tests for the behaviour you change.+- Describe the change under `[Unreleased]` in `CHANGELOG.md`. The file follows+ [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and versions follow+ [Semantic Versioning](https://semver.org/).++## Related projects++- [haskoin-node](https://github.com/jprupp/haskoin-node) connects to peers and+ syncs headers and blocks.+- [haskoin-store](https://github.com/jprupp/haskoin-store) indexes the block chain+ and serves it over a REST API.+- [secp256k1-haskell](https://github.com/jprupp/secp256k1-haskell) is the binding+ to libsecp256k1 that this library uses.++## License++Haskoin Core is released under the+[MIT License](https://github.com/jprupp/haskoin-core/blob/master/LICENSE).
data/bip341.json view
@@ -445,7 +445,7 @@ } ], "auxiliary": {- "fulledSignedTx": "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d"+ "fullySignedTx": "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d" } } ]
+ data/bip39_vectors.json view
@@ -0,0 +1,1754 @@+{+ "english": [+ [+ "00000000000000000000000000000000",+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",+ "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",+ "xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "legal winner thank year wave sausage worth useful legal winner thank yellow",+ "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607",+ "xprv9s21ZrQH143K2gA81bYFHqU68xz1cX2APaSq5tt6MFSLeXnCKV1RVUJt9FWNTbrrryem4ZckN8k4Ls1H6nwdvDTvnV7zEXs2HgPezuVccsq"+ ],+ [+ "80808080808080808080808080808080",+ "letter advice cage absurd amount doctor acoustic avoid letter advice cage above",+ "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8",+ "xprv9s21ZrQH143K2shfP28KM3nr5Ap1SXjz8gc2rAqqMEynmjt6o1qboCDpxckqXavCwdnYds6yBHZGKHv7ef2eTXy461PXUjBFQg6PrwY4Gzq"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",+ "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069",+ "xprv9s21ZrQH143K2V4oox4M8Zmhi2Fjx5XK4Lf7GKRvPSgydU3mjZuKGCTg7UPiBUD7ydVPvSLtg9hjp7MQTYsW67rZHAXeccqYqrsx8LcXnyd"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",+ "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa",+ "xprv9s21ZrQH143K3mEDrypcZ2usWqFgzKB6jBBx9B6GfC7fu26X6hPRzVjzkqkPvDqp6g5eypdk6cyhGnBngbjeHTe4LsuLG1cCmKJka5SMkmU"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",+ "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd",+ "xprv9s21ZrQH143K3Lv9MZLj16np5GzLe7tDKQfVusBni7toqJGcnKRtHSxUwbKUyUWiwpK55g1DUSsw76TF1T93VT4gz4wt5RM23pkaQLnvBh7"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",+ "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65",+ "xprv9s21ZrQH143K3VPCbxbUtpkh9pRG371UCLDz3BjceqP1jz7XZsQ5EnNkYAEkfeZp62cDNj13ZTEVG1TEro9sZ9grfRmcYWLBhCocViKEJae"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when",+ "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528",+ "xprv9s21ZrQH143K36Ao5jHRVhFGDbLP6FCx8BEEmpru77ef3bmA928BxsqvVM27WnvvyfWywiFN8K6yToqMaGYfzS6Db1EHAXT5TuyCLBXUfdm"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",+ "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",+ "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",+ "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",+ "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",+ "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",+ "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",+ "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",+ "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic",+ "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028",+ "xprv9s21ZrQH143K2oZ9stBYpoaZ2ktHj7jLz7iMqpgg1En8kKFTXJHsjxry1JbKH19YrDTicVwKPehFKTbmaxgVEc5TpHdS1aYhB2s9aFJBeJH"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog",+ "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac",+ "xprv9s21ZrQH143K3uT8eQowUjsxrmsA9YUuQQK1RLqFufzybxD6DH6gPY7NjJ5G3EPHjsWDrs9iivSbmvjc9DQJbJGatfa9pv4MZ3wjr8qWPAK"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length",+ "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440",+ "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "scheme spot photo card baby mountain device kick cradle pact join borrow",+ "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612",+ "xprv9s21ZrQH143K3FperxDp8vFsFycKCRcJGAFmcV7umQmcnMZaLtZRt13QJDsoS5F6oYT6BB4sS6zmTmyQAEkJKxJ7yByDNtRe5asP2jFGhT6"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave",+ "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d",+ "xprv9s21ZrQH143K3R1SfVZZLtVbXEB9ryVxmVtVMsMwmEyEvgXN6Q84LKkLRmf4ST6QrLeBm3jQsb9gx1uo23TS7vo3vAkZGZz71uuLCcywUkt"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside",+ "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d",+ "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "cat swing flag economy stadium alone churn speed unique patch report train",+ "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5",+ "xprv9s21ZrQH143K4G28omGMogEoYgDQuigBo8AFHAGDaJdqQ99QKMQ5J6fYTMfANTJy6xBmhvsNZ1CJzRZ64PWbnTFUn6CDV2FxoMDLXdk95DQ"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access",+ "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02",+ "xprv9s21ZrQH143K3wtsvY8L2aZyxkiWULZH4vyQE5XkHTXkmx8gHo6RUEfH3Jyr6NwkJhvano7Xb2o6UqFKWHVo5scE31SGDCAUsgVhiUuUDyh"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform",+ "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d",+ "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "vessel ladder alter error federal sibling chat ability sun glass valve picture",+ "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f",+ "xprv9s21ZrQH143K2QWV9Wn8Vvs6jbqfF1YbTCdURQW9dLFKDovpKaKrqS3SEWsXCu6ZNky9PSAENg6c9AQYHcg4PjopRGGKmdD313ZHszymnps"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump",+ "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88",+ "xprv9s21ZrQH143K4aERa2bq7559eMCCEs2QmmqVjUuzfy5eAeDX4mqZffkYwpzGQRE2YEEeLVRoH4CSHxianrFaVnMN2RYaPUZJhJx8S5j6puX"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold",+ "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998",+ "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS"+ ]+ ],+ "chinese_simplified": [+ [+ "00000000000000000000000000000000",+ "的 的 的 的 的 的 的 的 的 的 的 在",+ "7f7c7f91ef81f0fb6a3b95b346c50e6472c1d554f8ba90637bad8afce4a4de87c322c1acafa2f6f5e9a8f9b2d2c40e9d389efdc2adbe4445c21a0939fb39e91f",+ "xprv9s21ZrQH143K2LTAgxJMxVMKie6n9HQHMUohP6x2cx1TVBr6dxnL3mnSLRiXjiCM7g2ZF3BHzpdbFuhdeh7ZRrzv2EEjg5Tv7kgKZrqbVLc"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "枪 疫 霉 尝 俩 闹 饿 贤 枪 疫 霉 卿",+ "816a69d6866891b246b4d33f54d6d2be624470141754396205d039bdd8003949fec4340253dde4c8e11437a181ad992f56d5b976eb9fbe48f4c5e5fec60a27e1",+ "xprv9s21ZrQH143K2t2fMBqtVAVWU3JSpmEbbddwouLX8NoBbcTykD1Tm4s9api6K9zvoKSESUsA7aVxbZRunM5yrjZRNZnZckve98hxUorv2Uv"+ ],+ [+ "80808080808080808080808080808080",+ "壤 对 据 人 三 谈 我 表 壤 对 据 不",+ "07b6eada2601141ef9748bdf5af296a134f0f9215a946813b84338dcfba93c8247b0c3429a91e0a1b85a93bd9f1275a9524acecadc9b516c3cf4c8990f44052c",+ "xprv9s21ZrQH143K2cgeQUKgCSmaRVXFjEGThqrnNFmH71qG8z3bWqYcbX9zakkRxmDp583tqf3cQzmxtn4C2XqinMNb2HkhXBDYhekCB8AwZWV"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 逻",+ "08ac5d9bed9441013b32bc317aaddeb8310011f219b48239faa4adeeb8b79cb0a3e4d1cb460d2dd37888c0a19bef6edd90ced0fd613d48899eab9ee649d77fcd",+ "xprv9s21ZrQH143K3Zkkh1w8EbXYQWAS5ekbitA2WVrswJY9uEJzig2BtairT72n98ySwQUAhYBsLW9EBjJ1XUinrSb69Ty4mttMLnaUooJwsJ3"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 动",+ "b8fb8047e84951d846dbfbbce3edd0c9e316dc40f35b39f03a837db85f5587ac209088e883b5d924a0a43ad154a636fb65df28fdae821226f0f014a49e773356",+ "xprv9s21ZrQH143K36LufUjTLqXnTiY6ach28pUYMkJ63swx4FhjPkzqD9YRqDZ452whYcNzKpPC8yfBm1eomL2z3VLC4zcwU71oKvMQ5NnD4h1"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "枪 疫 霉 尝 俩 闹 饿 贤 枪 疫 霉 尝 俩 闹 饿 贤 枪 殿",+ "74187bbdce2dba25eed3b9aebdc65dcb7c61e74c58591451d47f9c7b7b17545a527880640bfb9cab36989eba1edddf57bfce7340697926de7f0b9ec1e0345c38",+ "xprv9s21ZrQH143K47jLpKzSgdpMLSKP2ZMLXHHUqYYGKbXNE4k3TA2czvJCx5JAJFNkWKZf2B1AbYoUBpc96YoiwM7yfxyr8gvfNNgL7sFNum6"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "壤 对 据 人 三 谈 我 表 壤 对 据 人 三 谈 我 表 壤 民",+ "e3629a601f4b87101c4bb36496e3dbd146063351f5e47c048211faddab78efdb91910f0eea5c8e53cfb851aa3e156b0bb5c501b83baaf5f5d4a1679a5bb7d885",+ "xprv9s21ZrQH143K4RfeWihCeh1FJL9SobvinRW4z76RL2X1TB6xreXgaMvJGggUgagkaNr7zX47YHEDYdzJmig8SG3Scuet8smspn7HicCtwHa"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 裕",+ "013c8d6868537176fac7bfa966e6219830008f03b650b0f18a12fd67d9ebf871c400c5f980aa073ddd1b23d60846e357aee193ce7644b574bf65e04cf913e39c",+ "xprv9s21ZrQH143K2YiskWzQq8kpFFCoFKKU4L8D6Y593dS2sExuVQ4GjnS57RhibwTWjnD7NTCE7ye4cQbCK6Bw674SHb3xWaQYH3NBLFCGYJb"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 性",+ "1981c3e3ddfd80f6e9ee1c5ef27ba2697df3d1468496f1d56ae3d8e0b3f0677bbbdfca954e48eb86fe6a36fc0f597bf18ea00248757a01e82182badff94abbbd",+ "xprv9s21ZrQH143K25ttGBGbx6h9VBpa9ELbpw35XQqDR8deXRyVP2AbtgJ79Nq2cW8KaDizbwuoHUYR1o4tLPhYvSCTNMft4ZEfiDztmjXKPCj"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "枪 疫 霉 尝 俩 闹 饿 贤 枪 疫 霉 尝 俩 闹 饿 贤 枪 疫 霉 尝 俩 闹 饿 搭",+ "b1eb831927f1c488e233725f9c409dd9bdb9342324393fa56d958e8842623d222510c322f5ba2899428ae08ece8bd87788748c67bdfa73588669ab816c5f3555",+ "xprv9s21ZrQH143K4VNNDqCgnETDkPiihzHpxC9wGE6TBGXaeEd9VkQRHQPotwheeaNFGHGKaPWv5zTqfknzgdWiKFC6DqaqBFKjNSmxas968Vz"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "壤 对 据 人 三 谈 我 表 壤 对 据 人 三 谈 我 表 壤 对 据 人 三 谈 我 五",+ "470e61f7e976fa18c7d559e842ba7f39849b2f72ef15428f4276c5160002f36416cd22c2a86bb686d69f6b91818538aa57ae1aab27b3181b92132c59be2b329b",+ "xprv9s21ZrQH143K3bUoGmLq8aXRKzvUhseqrXw1t7XYCirduP5XLJtVxCos3rYLDvW8V7pK3voZ4EWSdeXiKdbWNjxmiPRDfet23Av9VRaR3ej"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 佳",+ "8e6607a07fa664d6e4ead23fcc08caf72216d6f078c3b2e5be94e4b6e8d64c784d36bf9b70144fa05840e9a49899128111be5093a2b552b6ab76c0906e9b0e65",+ "xprv9s21ZrQH143K2ghKxX47TRr4GnQh3diJFN5rJfybjxuwr3xP6pafXrBhwXJsw4HwoiPZ1f6fFPR964eoXybV2su498Ant3kYuYKE3CszLsU"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "蒙 台 脱 纪 构 硫 浆 霉 感 仅 鱼 汤",+ "decd71d2824a1bbadf8c3942f43504a648a8db5f1cac0ae1d0f787728353002a12644b1a6b725147c91682e7f33aec13493b9a779a7dd8ee15a5d10ab21d49e5",+ "xprv9s21ZrQH143K44Xrktko35a7gPGVo91Va79LNer1MzVokcrYKFP6GMLAcuJP3fBSdbRuG2DauFC48H6LmyZLfkUyjpm1R2AxYVbnT2P5tur"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "父 泥 炼 胁 鞋 控 载 政 惨 逐 整 碗 环 惯 案 棒 订 移",+ "ff66373b70b72b34842f936bf3bb44d661fdafaee7740d574fed6aa2ef07783cb6111f2862cbd3fc5528e322dfe054557a74a568a1b46c020cb88938e2293ca0",+ "xprv9s21ZrQH143K3JLu5XeRDQA5RwHh6gUXaQfRf7ihtVguJvd6EFoAzsoyotvNTDZfC6cciies7fhcqaMRTEsZUSbRYqBdaviWRHatRMoHX6s"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "宁 照 违 材 交 养 违 野 悉 偷 梅 设 贵 帝 鲜 仰 圈 首 荷 钩 隙 抓 养 熟",+ "6ba622f907c61e29e44833b08441b7afa84889a48ca90ebf90f585e257662b2c1b0c35ad54088e745c73689921209fdd4b5b8ace5d850e366d7c2042a076e660",+ "xprv9s21ZrQH143K3dE2RQqFbapci6WBj47vZCkLVf4r14QsRZ6Ny2ck6s8tzZQzQaM55Tt4d2tRS9AuFrEJ4yBXsmP8yKeVGXT8E97iRVkDzCj"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "伐 旱 泡 口 线 揭 县 杨 断 芳 额 件",+ "7346996be5f2b02c67ec465c677197375b589b6e8871c842505b139c2d47feca75a2a941623d6486aff6b21c95193a8177960d123cf610f03f3224a9fa7d0eed",+ "xprv9s21ZrQH143K3A2p7cttKM5L39rutYgY4jqZ16z7hpAEdyiT8fr5eKdaHGLMM2ZmsUFNvGcSfyGMp1sz9nVyg3ZXop1hesUbgAvVBzx4rix"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "福 惜 怀 叔 筋 酵 货 科 牙 冒 辈 罩 悬 耕 浇 呵 连 级",+ "09098e00fcc1bfa7d5b9f0c12dfe1993bbd5a0915200a53fb40b2d6d487b969a18463565c1e035569796a7d8b99f82a4c4b17002b0c582037da95bacfeb422b3",+ "xprv9s21ZrQH143K4LdjFgCrosa76XBrNQ4imGB7XNc5MhfkASrmAHVNWYLNGY6kNj8foXBLS9aUD8RDubyj3NKWarmXxRQ28AeWVPbLD3a1FEq"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "仪 未 九 茶 队 梯 妇 孤 托 病 泉 贺 产 绘 吹 测 局 碳 征 墨 晶 帮 息 延",+ "6d55f2dd8d42f1cc5e0b4ef6e8a95200580ff4e29d2a3dfa7f9ddb1af0aa2e93780d84d952d39776a379ddc017847ea01aa01b85dc208e7f69891d5b7cbf2eb0",+ "xprv9s21ZrQH143K2tkCCjLXj2L9Ds9FttWwH6Eqt3uvihwUvpSpTm3RmRtSkapxASEUW5HXE6Qx2H1viBxXbLZVKxyfQ7fyFn6NcDspgJfdPaT"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "济 扶 块 言 穗 定 万 绘 姻 逃 颗 焰",+ "bf8dcc2fb4dc8fd2311943b527864feabfebd5fffb6641555519da3606265e895bab5aa1647f6e5afb0cb6ea4d0b27e8a9f2f49251b68ad6bf898937581351cb",+ "xprv9s21ZrQH143K2i53x2geCJi6A2QJpaHpxeGK8oeMxYs1tUrUwU3ddu9jHVucF3ePQ1koQ1TVFHnfP21ScWzp8xKnMinNUSW5itm7PicXWYW"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "虑 铺 目 祸 英 钩 尤 添 醇 嘛 触 独 起 赋 连 剪 邦 中",+ "07e1a2dc2eea79bb12be53d6fb662edf87796cad60e8d10a655ba39a95c5a68eb21f865a1b2f37d780286adbbddeccba3f7844c8a2b1a82029e6a855c713aecf",+ "xprv9s21ZrQH143K4V2oh58ZSb1CAbYkwB6ThJmTpRC51uKha9hy51R6RVzzbcEog3n6h4u7FtotS9arBvLofX7A3Apvcaedmeg1jkt7Vkt9TtA"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "而 怕 夏 客 盖 古 松 面 解 谓 鲜 唯 障 烯 共 吴 永 丁 赤 副 醒 分 猛 埔",+ "0402ae511062cfacbd5e33637a95e57e2e14fde0c5dd471fe66fc1154b6373802aa8641a78b91658052bff0a5c5bd075f01fc74b0d73e95a890430ff6f0e728e",+ "xprv9s21ZrQH143K4LSoFcKBWtmUHjzuH56srzrkpPfGx3i36UXpVUEtGvCuZ3egRTyDkaqRN5Ec1HXvXGSGTXLyAKzCRnTspi1D9NdmQmWqM5x"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "昏 途 所 够 请 乃 风 一 雕 缺 垫 阀",+ "aa7e38f64810007db63e31c479b9848cd5ffda839546749669bf53476dd036a33fd77d0a13d4418fb536ea78b028fc19533db4bc9e0e12a14a9432cb9fd112a2",+ "xprv9s21ZrQH143K31afj91bWiGw2aC2xHJwrWsMs9MuvEWZETkpWr15ZNF6LFjkHh53iD2bwJYEawvCCvDRaqDsr37fhapgGDkA7UATtwBsZu3"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "瓶 顾 床 圈 倡 励 炭 柄 且 招 价 紧 折 将 乎 硬 且 空",+ "2a6181cf2b069ba30a87228d54770ed5abf61e8151abdb0b27646a87a6100d4b7b496c3ca26f027d0b06724c6c5a469f43a7f1ffb7782e5afb01d143ca65973d",+ "xprv9s21ZrQH143K3ji6aQ9QQi4WkFhAqnrzRANJkYkRWkMqbr51mAV6JYUt85oR8Jt3D7DreQdsb9292ips6VKwpitAhLR7rfLdhYz7zgYei2F"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "柄 需 固 姆 色 斥 霍 握 宾 琴 况 团 抵 经 摸 郭 沙 鸣 拖 妙 阳 辈 掉 迁",+ "4dccb0a3578716975b840c51e279c2af728567ff42e98dd09b9e61742b41d9f30d411a501172cce9b7d5706a480dd4d4e7fb26021a36a74381156b09d251d65a",+ "xprv9s21ZrQH143K2hLNNA7KnimwonwFXCiGVM3K29DgZiTcVUJ8t8jkc2mXUzFZmoFgXWh9UhJFbyKM44Qm2KeGsrEevajZZfKyoLyFmyoDpUx"+ ]+ ],+ "chinese_traditional": [+ [+ "00000000000000000000000000000000",+ "的 的 的 的 的 的 的 的 的 的 的 在",+ "7f7c7f91ef81f0fb6a3b95b346c50e6472c1d554f8ba90637bad8afce4a4de87c322c1acafa2f6f5e9a8f9b2d2c40e9d389efdc2adbe4445c21a0939fb39e91f",+ "xprv9s21ZrQH143K2LTAgxJMxVMKie6n9HQHMUohP6x2cx1TVBr6dxnL3mnSLRiXjiCM7g2ZF3BHzpdbFuhdeh7ZRrzv2EEjg5Tv7kgKZrqbVLc"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "槍 疫 黴 嘗 倆 鬧 餓 賢 槍 疫 黴 卿",+ "f38af46f6bc3222b0f5aa14dd5b8b506e51131510f2450ec9fb52c28617cfa59d436055fe542e25dfa01415639d2171e41796f169f8bbc18516941dfdee8fb72",+ "xprv9s21ZrQH143K2HYJ8dR81cQGust8Gm4MeyfC6off5BvCfffAxE33WhiYxB4aLV6meXP6QmoKZkLX8UJgrZPcA4A2EKU4iaPWenb6Wg9kxzd"+ ],+ [+ "80808080808080808080808080808080",+ "壤 對 據 人 三 談 我 表 壤 對 據 不",+ "33f373da1a6b4300dad5cc70d2329ed614512e3c8a423673c294110521326ca66753b9663bdd7c844f17d81609a410a61809dd5113823009f729e2f2f940cab9",+ "xprv9s21ZrQH143K4SV7MzxYhSQtxP5gWGCD75QiUsp7z1s7Kuc8b4V4F6wRqKvhdczy3qi2uNeN9Vw4PnKYJtoaFdHFX4qbxweuRDmQqgMjKRJ"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 邏",+ "cfd5f4fa6f2a422811951739b1dad9f5291f9cbc977a14ae9dd35dc8ab17aeec9ee6f1455b20f881838f4f945850765dd002a9abcdbe7be002ffcdaf6f63fdaa",+ "xprv9s21ZrQH143K2BptXPaTm7CCWCs4v3tfG5jGAa9zLqSTKNL1ah8veMWc53hio5grKVriWhKjNKbCA2w6svkL4NC6pkiRwVkbwazVXtjdgEv"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 動",+ "717f4f70c7550da57e42c6b49ac47b5bad3249605ed2f869900596c2de7653a8528380e5c31709ed9c2d19b868bc530158712e97276886b4863d036177bcab33",+ "xprv9s21ZrQH143K39TvGFp5nfiw3zXib4v4waTbxFYoDNoD4pp4DMBneg9MgSqfK4xL7hK1YjDEa5BWMXKVgTxMiNXNSdT3Wv59pD4PB3LDDKP"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "槍 疫 黴 嘗 倆 鬧 餓 賢 槍 疫 黴 嘗 倆 鬧 餓 賢 槍 殿",+ "2b219a8be0a8e27a6b50d0a74eb42175bd23e22cf4081518c9a74cbfe2cbace46f0adad8d390f8a2ac30feb26226db14fbc545d18ba0e56a853cbf103c92539e",+ "xprv9s21ZrQH143K2wC9BUJ9F5CsmV7a6PJQkxc9TT8gUrkoTXurPztKKosq5REGzdEzEuKn221vm2A5KnjrDBC1KcLo4VeYGSkkwxXWYrqSrXW"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "壤 對 據 人 三 談 我 表 壤 對 據 人 三 談 我 表 壤 民",+ "d29225f73231521784d98820ebf0ae4d827c5a9e0c0f8845fd63866cdc70b3a40a2281f3f6c6181c5a53e440528dbf83947a4b2056749cb9cc9c83dcd5c91b0f",+ "xprv9s21ZrQH143K2MQFKxX1ReLYY2rNunsfLpU8F5WRVWeLfauPhD5huvXEzxoPvmLhD3QtSj1Z5jnM51q9NrRjYBaHG5XJzfVsjXWYQop9pXz"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 裕",+ "013c8d6868537176fac7bfa966e6219830008f03b650b0f18a12fd67d9ebf871c400c5f980aa073ddd1b23d60846e357aee193ce7644b574bf65e04cf913e39c",+ "xprv9s21ZrQH143K2YiskWzQq8kpFFCoFKKU4L8D6Y593dS2sExuVQ4GjnS57RhibwTWjnD7NTCE7ye4cQbCK6Bw674SHb3xWaQYH3NBLFCGYJb"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 的 性",+ "1981c3e3ddfd80f6e9ee1c5ef27ba2697df3d1468496f1d56ae3d8e0b3f0677bbbdfca954e48eb86fe6a36fc0f597bf18ea00248757a01e82182badff94abbbd",+ "xprv9s21ZrQH143K25ttGBGbx6h9VBpa9ELbpw35XQqDR8deXRyVP2AbtgJ79Nq2cW8KaDizbwuoHUYR1o4tLPhYvSCTNMft4ZEfiDztmjXKPCj"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "槍 疫 黴 嘗 倆 鬧 餓 賢 槍 疫 黴 嘗 倆 鬧 餓 賢 槍 疫 黴 嘗 倆 鬧 餓 搭",+ "fd50ad67903b2046356e67e55d67309b6f0ccd7c23bfefd049a5b8a40d56c507d73a5517e2d2785f024a7794854594aaad845dd0fbd0432c25a96f2a7181a2cc",+ "xprv9s21ZrQH143K4TP9sQD1LnxuSy6WUe1hF7JMPo4qN6TMX5udfEcJh9x4PqbetYoC9c1hpx7RxP6VcgzdPxPCJ91De4R1TgGNVC9AFhxMwkX"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "壤 對 據 人 三 談 我 表 壤 對 據 人 三 談 我 表 壤 對 據 人 三 談 我 五",+ "d029fc9737b801cb4f9aadf5feed02a117b76ead7058e055cc39cb44864023eb492e6a15c68569d6a03a5b11bf15a456c64e1781a553589b47ab569801239a00",+ "xprv9s21ZrQH143K4bYXrWpQYgiSHDM1iVkNVnyqbpjyZksLS2fmxKCbwQjz3sBFTD1aFY4xWrYdHTyeFYjnYWfKGLc5WCkpokdAZyP9XEGJCsa"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 歇 佳",+ "8e6607a07fa664d6e4ead23fcc08caf72216d6f078c3b2e5be94e4b6e8d64c784d36bf9b70144fa05840e9a49899128111be5093a2b552b6ab76c0906e9b0e65",+ "xprv9s21ZrQH143K2ghKxX47TRr4GnQh3diJFN5rJfybjxuwr3xP6pafXrBhwXJsw4HwoiPZ1f6fFPR964eoXybV2su498Ant3kYuYKE3CszLsU"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "蒙 台 脫 紀 構 硫 漿 黴 感 僅 魚 湯",+ "27ca577f0318b6c6067acce7aefacd12bc9fbbc8e365fdc16bfc0ffd76379b0768dc56877f19eee4c1222dfb5a94a5516c5707e6a6ad070af9a0fe7f7799ac5e",+ "xprv9s21ZrQH143K3FtWQPZHP7Gpf5qgbvXqPNo5iCSfkGhWnATeqM5FuQ3YTx4sSciJx1MjVnbM3XQ16N83x5gNwcsVG7PTf1cRDhvyGZ45EY5"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "父 泥 煉 脅 鞋 控 載 政 慘 逐 整 碗 環 慣 案 棒 訂 移",+ "fcac6cdda6c67e46ea46e66d00df3cfb1e437aa05f1b280f5427c0ce521a94b5a01ab016d235b7944f36d76ba0a297968ae0d882fde95c96cae34e35f2433c82",+ "xprv9s21ZrQH143K4YANCnajhJxfsFsDEZGitTmP934osTHvUmTEtfSaYk8rmvj914uGUYaJH5ALjDgVyNYW5gRGPdQBFaqoSbGkHXhfdVkNVY2"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "寧 照 違 材 交 養 違 野 悉 偷 梅 設 貴 帝 鮮 仰 圈 首 荷 鉤 隙 抓 養 熟",+ "969aaf00b9af97a1c3fd0b7b35480aebf51577658067df966caaf5cace472d2ecdaa2978470be83463262340527c0564d8c57f86764d48e9bebd1ce594955a6e",+ "xprv9s21ZrQH143K3aULbWnFhpGS2h19R5JdizbLLLQTDsGmKP5A1sqQop1Ff8Q7NEetRHRNFR8AAsPm1kr5hGU5VLYKURZeiVe9k5hqVmhZ5zF"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "伐 旱 泡 口 線 揭 縣 楊 斷 芳 額 件",+ "09c172005e7dd81fcd55b87d13f114207ce7726376ea74a1b9085a799b2afbd5ac5526059e722987a65f858e5301edd5f4c91deaf9d7b4f9bcc38919e5ec3725",+ "xprv9s21ZrQH143K2sMUzpWRrPMF1Xx29Yos3Kah6H4E9YaJqDm2yEMXypoKdX1ugkn1Vx3k9Pr2LsKNQnHqoDFP5Jepm2PCkUS1GrQZps1wxkD"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "福 惜 懷 叔 筋 酵 貨 科 牙 冒 輩 罩 懸 耕 澆 呵 連 級",+ "3e09d89450ae45cc1a07ab308649f291ad5c1452da509d7269daef52ddd04db8bbb6bcb8a71322c4d25ed4686d910e84156fccfbac2838ba482bdd1e4b2ea693",+ "xprv9s21ZrQH143K4bLxh3ir6ziqU7t6URBK7uA8Md4tzAd3TAuBNyZREqEoZBevwu8Uw6tfMujFRWrZvCy47nbnKgDhsFaC2hvFJwRdkhnoM1v"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "儀 未 九 茶 隊 梯 婦 孤 托 病 泉 賀 產 繪 吹 測 局 碳 徵 墨 晶 幫 息 延",+ "d687bb89cb435fe1de166e953b41500f3717a497ca35c78322f66cd63e675fe0c8aba92463544631cdd6a985db03bdfcbfd839002ec609879e8768a3ffdb5fea",+ "xprv9s21ZrQH143K2LB4bPuHTErkGLt8DV5BbRsS82ySFPdQr57fzBY5D7VadhJHruxFzVcYdEbDqK8QWPSqj6LCiyQgFFurfnWWm1v6N2nGovN"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "濟 扶 塊 言 穗 定 萬 繪 姻 逃 顆 焰",+ "806655cee21d12c952d6a11c12e742809c4452b6e07458c6ddc2cc2a8920e308476f3c6ba7fbbdab3de3a7bcecd4de5dd82dee7a217d0cd071eaa2313ca390da",+ "xprv9s21ZrQH143K3Kcz7sz3UyDYJhSjUYujBdxFwDHCbqmKWqN3hSf1pjnqoxuAKsQXbnvrxzRBLqPN9BKxHUiYgn1DjhwaStTfZQTCsaTEWxZ"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "慮 鋪 目 禍 英 鉤 尤 添 醇 嘛 觸 獨 起 賦 連 剪 邦 中",+ "b609a4e17fa8c3c0b4da704b1699631f0d85f5b7bcc7d1488270551670b5393a0dfcb4d8eba9860c2c211324bbf3b587763ad1ac6a9e61a4e2e015bb6cc6a58a",+ "xprv9s21ZrQH143K2Gmewuzc56mbdKTqPezqWqk7ih9AdnLc9A2865TYtywU9sZ547mKYFKnbVatAuUUGtqgAz1ENkv9FU85jARHkCmtAGLmExn"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "而 怕 夏 客 蓋 古 松 面 解 謂 鮮 唯 障 烯 共 吳 永 丁 赤 副 醒 分 猛 埔",+ "8ce6b92bf95337a49bfd3d80774c9a73d05046eb2cb41789092a3bfbe7005ca668c427a42f1a93982d9076511330817b6d0bd49ba4f5a39e5756472b162f7ba0",+ "xprv9s21ZrQH143K41g7SsRpGA4g25xDiqjkhq8DNbJD7jgSksvt9kakaTzukN4SfVrUM2A9yGVPggP9eFAmuriRSpPLahm4ngaxvNHyQp82cee"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "昏 途 所 夠 請 乃 風 一 雕 缺 墊 閥",+ "e62457aa7f30c24fa46b90aeba2cbb9e77c28fcafa0c10dab01f5323eb1cef22f23c0e52cb5dffa2b2911a29992213c2cb20564af268eed03ea11292fff1a737",+ "xprv9s21ZrQH143K3JPEGRnwbdy5xdTvVdXEDGJX8gc385oC3H4veWiCVDrdeeFPWrU7Pz3PNMHcZrbutLY1UGs5dE83U925xKXLUrXXpz2ihKw"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "瓶 顧 床 圈 倡 勵 炭 柄 且 招 價 緊 折 將 乎 硬 且 空",+ "6b5591c758a069d3425bf93399398e8ef3e1c32c27f46e0a5284976dcacf25895f5d7747b84f38596247557debd133576932d394ad24c7a00aa24555fa668c5b",+ "xprv9s21ZrQH143K2hh1em7CqtG7uL1bTNG5hfP35X6Y8uwnTDiS9Cm87WNcZvmFHLf3JwoP2W82VzaRejpqr7oPnFLguhVSr5pWnG9YSV4SdeH"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "柄 需 固 姆 色 斥 霍 握 賓 琴 況 團 抵 經 摸 郭 沙 鳴 拖 妙 陽 輩 掉 遷",+ "17ec1a79121f3541e2d78ece35c8cfe7f5763b39d93fa90492c4beca26ee69d3aa7f4b1e6a2ac5e8225e08dded19357ee44b852dca425792842ec8eae09ae43f",+ "xprv9s21ZrQH143K4RoVseL4UENdN7Ag1WmcK7Q6Pk329krQW4RifHJ5sNizkG1PiyRXAouyL7KDFJSQAD1VarGTPftD1yZZAni3QczW8V5gNVG"+ ]+ ],+ "czech": [+ [+ "00000000000000000000000000000000",+ "abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace agrese",+ "872501bed75c98fbf943a67907bf394995f337e9adfa23687282d1135c262421715a0bcccfe2d3f5f8b72c8e2fa12a7a7267f8047b744557f4a9d49d11ccc75f",+ "xprv9s21ZrQH143K3rnjkVvSaFkwgg1J2tnUAeqv8SCEWTWdLVZiJsjM6Z5ieeUnrR1Ws6sDb8Guqp43CXVRmPooUs2cjwefSXh3EyXx2vmzZ96"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "obrazec znak uznat zubovina zeman skupina zrcadlo vzchopit obrazec znak uznat zubr",+ "68e1bd31ed5f20c9ab108c03b524e85209b0b27af80cb5d48fa71d03dbb528b73c2349bb8576f9b68825272984061594f520e54605a4898ba61c433d06bf5de7",+ "xprv9s21ZrQH143K4JXKqKovjKKSb7zgQcgbuKNNjaWyYBjjdny1SyXiPbjzSNUR6uLmNmPJ1NcWzeHDLqmopeTTbZ3DbPobsm4pbDrNrqhEDbK"+ ],+ [+ "80808080808080808080808080808080",+ "obvinit bageta doma amputace bidlo jedle arogance butik obvinit bageta doma akce",+ "067089f8edbbb8bc8ab6d0f3e29f250d136955745797a20b63fd4372627c51c4576ebd5fb6c6d4825d21f448cc24b342ce3b0117fedf41369cb5a6be77494aa7",+ "xprv9s21ZrQH143K4YKjBNHoBtUM2qhAvjVvXsBZJGAhogbqhBbgTvjRjPSgC5ShPUeGRCRTdRMYLZ7wWxRWwcvgQm9xX7b5udk9p5yfiekNMQK"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zticha",+ "04d0a733d43c640a4492b670a9549c60a358a681891cc2337a01a3c8288cd2941b7e057dbcf2dffd1e614cf5fcc9d38d9228fbd3ea5ceb508b8aacac5f35ccd9",+ "xprv9s21ZrQH143K3pcHonm1bGBQvbLf6Ty3c7HKCyzFr4C4DnvLPaT1JUUJ4MxkWyNhPpCjVsLMJEndqUocNxhqZ3UfFwxAsn7zajkE7n3yXYq"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace balonek",+ "b5eb0b74cb5f2c616e7136182597ab61dd94594d22f15ce6c94e04eb7336a56d3e445ec1279c1f04b861de5f7c6b2fc95227db53be4996de3ba87d6d76b09098",+ "xprv9s21ZrQH143K2Edeaippdsom8CKbdVxcorYqTexQoxhdci7pVuv6K1yFYnGunPP3d6oKyE3ax354UgfNUu6Kes1hvjvJcoyHfyjxwbjhFqM"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "obrazec znak uznat zubovina zeman skupina zrcadlo vzchopit obrazec znak uznat zubovina zeman skupina zrcadlo vzchopit obrazec zmije",+ "93ff13dee31715a6568609df3f7ea295d58728a65611ea03620d2105a0efbbaa39d8b6541b3b5a57a25dbdfd5006f0c58779a7ed196e25a1a97d1442e3f080fa",+ "xprv9s21ZrQH143K3SW9C3X4jc47HaXuDHjRvpCDxoo7PgTpMG5EZoFkDCkCzhs2yMvaUQidKKXBDChe2ns524iFEbrJFqNCPFvR1bjyRmq5rxj"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "obvinit bageta doma amputace bidlo jedle arogance butik obvinit bageta doma amputace bidlo jedle arogance butik obvinit bezinka",+ "1843be39a115dad287e10d256d2e9bb81244cefda2b7ead8a762f53033512abc7b6db26e2ebe8053fb82e313c24bcf62ae84ba4aa2900ca0fcdcb1affc38887a",+ "xprv9s21ZrQH143K3uYc2wzd6UfPLXqfQKsYJVyWL2HeCZ1YKhqd3jt5y4w9bfLTf4bAqvD3NbJ8ifa6vbTvHL2t87wsgi22RXd8c8Moknbzdcc"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zlehka",+ "43b7d9b1b25d046f8a89fb57ba10bed11b5273574bb820eb01cc0733a421f1c98ceb2db42d299e7e96aa2c58435e916821bc9d505525b3b5448ecd4c97babe0b",+ "xprv9s21ZrQH143K3LM3GwVcGcoMnK9UmQKxTaq1GiX1HsG5u76FmfUi5cTcH97WSXxVNLgURJCgL8RT1qDNsiQ9Sw1DDVAhiu4h4xMz6mtFDzL"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace abdikace branka",+ "dd2a9f662649585707dadc6e8b2df2c0e0e2691d53bacea2212aff4063ab4fdc79b703a7ce6744da31cd2ee12e56b9ee0f430a238b892fa660ed0ce879f2c472",+ "xprv9s21ZrQH143K3RHkXA6CEXUxT6nXGMM1zKuxgGQBD6k9gErraXywHoiqx4syweAftp5SCU7XSoFz5T3RbPs3HNZtZFa5JcR9ZD5TUmV15tB"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "obrazec znak uznat zubovina zeman skupina zrcadlo vzchopit obrazec znak uznat zubovina zeman skupina zrcadlo vzchopit obrazec znak uznat zubovina zeman skupina zrcadlo veskrze",+ "f4e4e2d8817cbb3925d6a0e8a2a466dbe1353a5885ec203030722607b8b5f229c71066c18681fda4291d0e323e4f6ba099b5b7efff442adfa14124fd07147fa8",+ "xprv9s21ZrQH143K4bgmXSbkNXn4ntJoQWUwRscM7yiZjzHbBFc5nMmgMeVcVJS6ZqXcWaTp6Phy8PAji336TDSNmiSWK7cmuzCSxkobg11RTN2"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "obvinit bageta doma amputace bidlo jedle arogance butik obvinit bageta doma amputace bidlo jedle arogance butik obvinit bageta doma amputace bidlo jedle arogance cihla",+ "bb8b82baced0db7764e102d1d1f68035269e84ec6c1ed0e09b2a31094330967aff9e1a490a407fef736fb8719c60bfb8cb0be9b27fce97c3b619409c195e2f1d",+ "xprv9s21ZrQH143K2aufrtmAWe6Ppto9BA4jSHu3XmKbMemyWEJ37Zfwxie44JJi8pXDxfjkSv8LxdKKs9rmV1R9SxCfAtcttf6g2JK3mRwPyzM"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zvyk zavolat",+ "3991e1ccc78af78d58cae577b786f7c950e1f23311d0c5f6d51b884d6142a6b4fc91a227c895313bf3d35731682678653101f51546717d60438d54dead32f834",+ "xprv9s21ZrQH143K26BAmMqZUKy3rpA3bYvGiip1tjQ2gnLwKrm9zucVU6n5PSJ9FLWeW1JjQJyZJ52QAV3HtevGNQ3BnTh91mAWgaacd6UenQ7"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "pokoj jogurt malovat kroupa holub malvice rachot uznat hnout kasa karamel potupa",+ "f3922b8086d559436ba2d04bc2aae4174e6504d7d4d451f7282d0b41a1b8cc958b45a896985e0b9316ad09c62f7d62dac85bc3d3e2e2423bcad3336412fd33f8",+ "xprv9s21ZrQH143K3gt3UuAosyvKpAmRpGfWuM19LVhrpWshTnv8o9Z2SPx7PxfgPQSZtm7U34cWo2NUrUtWCtXLhKDFZxg9pjM9KRde9dLwsZC"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "masakr odtok pijavice tajga upravit krmelec krvinka buditel zavinit lakomec flirt traktor kresba plevel kapitola tlupa paseka ladnost",+ "8c7c2b7767caa63139099cc5faf955cc582abc43494fc0f94b1f490ddfb7c221df55b663711755602c1354862c1da7a1241d17888f1be6f4ba7f58634758ea0b",+ "xprv9s21ZrQH143K4TFHpb3sbWvTgFjmH2b2r6TAWi1L9UxyL2hBXQeDwVgQmR7CG3agahDGRPWJZDWZjcAYwEGjRzycaRzTmbmxjaJxujKScF1"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "migrace iluze pukavec kaktus drogerie hrobka pukavec onehdy suchar veterina rorejs cukr mihule koza makovice uvozovka oklika inzerce odhadce zprudka spousta namluvit hrobka obsluha",+ "4b315b6c57139dfd19187b6029ad8b2fc6165dd97a43e59c4606a11deb192b25df5ad4e8fc3b4c2da9b9e80eae48946d769bc7bc95786f93b934bbc842cb8002",+ "xprv9s21ZrQH143K2ro2PraMEHNWQX4w969ZUPVBZFY8pViiRNPhsRjFbj2exS12cg2YCa6kkYUCSJBhuHCicMAGqj1ZtSvKYXLjaErhCBve7XT"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "sledovat ticho potkan dotaz carevna pahorek ihned nikterak hematom pokrok neochota cvik",+ "ce64d48ded9f32127bc7ef66c829e32b576927ffa1f323f0020f58c3256fdeb5ee2ec7bc257bb492e4fa1ae1e7f41b8affd9f68a2143e2d54e443e54d866e6e0",+ "xprv9s21ZrQH143K24d38NZ3eGjiZ5cuqmVJjsC2piMq6ZPSsEfgPf6F9XZJvfsExo6M2P5NCgdaUYgeLchyBw8sAWkH8joq4yMeNF1i12zXWWN"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "most uvolnit novota usmrtit terapie tehdy litovat filozof radon svrab surovina zdivo stanice pejsek ukrojit vymizet helma decibel",+ "8efd8d7625a41d02e53b8f363678acb389136ee9b19512381417e7f3295cc5bea28a7feeadf29ed8c2bd617e67feee3c736bed06f29ed3538777d58187458955",+ "xprv9s21ZrQH143K3Vd6GfvEYmC1ut3Z9uKS5weiaKT77zoz4uc4MaJMnQWsXYFLK2wp3DiARFbUyEv8yLqAirLmWMDmsXFhPioprwBiqQbTPb7"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "poloha koruna dobytek makak domluvit svatba paluba utahovat orlice jakost sypat podvod barva technika pastelka kurt ikona obvod mokro recept napnout kabel lord nasadit",+ "e69afd8c8b83713ce327570ca2dd9d588dd1f266fde95d7848059fdcec016e8f9f587a8fcbdbd061ecd9a5e1e90f51f9453af914df7d8e9b5758f91a1963a413",+ "xprv9s21ZrQH143K2zXve6cknQXePKGSMLedfyGDpgQZ5oNB7YKgyJqjXoZeQ4qj1vd2HZpnF77g1nqihMeAi7wnYd3c3hrgczNAadCKdetb2tf"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "drak uniforma kuna kapka tmel bedna evoluce technika vypustit popadat rychlost vodstvo",+ "cb409aa4d44356187fc1f0777afc3f0057bad31090e589e1a8a13911a0604e9ebe976bbbd1633b3320049e58ae4939591150f1b84fc552975d4c7b5774efd20f",+ "xprv9s21ZrQH143K3S5abSGNCkH4QWovuVGtqjrufboCgkqDvPGUtnUgPFNqXwVyWMD45MmwRFDYUe7Ny89sqvCqxPMWtq2CDa14t1CYxLjefgT"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "ochladit silnice exkurze zrnitost jiskra zprudka pstruh tlukot vytasit valoun najisto kralovat bobek uklidnit helma ubytovna pysk andulka",+ "a295037a0335fc58e639e1eb4ad8c6679386cc0b6696c6c0947b9d4420083740c02a3c7e429c698b3650c5d370b87427ff094e17a18778d917cf5fa274c89b23",+ "xprv9s21ZrQH143K3se5sFVgT6vXcaaKoaHse8ATQJAgDHVBfdwAT2BDgm5YoEPJKR7NN7o5ukvzfKvjZ16NsLG6qngxkRfQRKuSsqyFmcyjpf9"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "bavlna mozaika ofsajd kukla obliba kormidlo monarcha batoh chmura mdloba makovice obilnice popel pohnutka duchovno panika neuron okupant rukavice kouzlo stehno badatel sklenice rozchod",+ "a3314b5d32a47a746f1605029ef41e446e589ec3879b8509a93779bdb5df018c102dc93d3925b1bc04badc7b7e78ed3c79f05485a289d3a8f4731282f4b65f2e",+ "xprv9s21ZrQH143K2NiewxC1w9akTDfjsfRQ7yyv42HTukKMZ2qqx9zRrq4YYoHUnEeSR9kVa8hY8vHzCYhPxzK8VF4vKJhQNAjGpn94j9r7nEi"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "zajet nutrie beton kobyla kriket sranda elektron abeceda uboze lump vzorek potvora",+ "a5fe5fd9fc7a02f2753029978da50e8c1af2a773977ecfda7147c184374376fc1780d4c2516d23eb1559e9cf46cc6f60d30418ba05fc789295adc483f26641ea",+ "xprv9s21ZrQH143K3Pg1Wcq1fLNYrSSbfioYAiYMqkY4bEN9JrUFnJNLsK2Z7b7stHtJae53w7GD7rrDjJ56CCMNYJzwpdC54pSFkCGV3VYwGbY"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "slezina navzdory odjinud oklika ucho ropucha rohovka zavalit graf panenka invalida katedra odebrat deska metoda ohryzek graf flotila",+ "73716db54d10471971852df2bda795452cfe9b38aaef918da7a2216a3b4249d76b4627b603a03537e5dc2152e54c709acba85d361f4978733af4b7366b13a61c",+ "xprv9s21ZrQH143K2YLLxKi6GZjifUEoqww1zAAUd26JY5T54Z6HgEip3hkFKUfAAeVqEmZrdoHoahhm71MvBwkMTZALYrJtZfe4eLqMyZE1op1"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "zavalit genetika kapusta tvrdost dopad ujmout zdobit mistr splav ptactvo fosfor hoch pocit beztak slon poplach mazivo tancovat pravda uvalit konkurs surovina nazvat vypadat",+ "dba03d22ab6f6963abff6d9f9433a6aa733490dab58b39e3eef635c9f4fd6ad8242c3eecc4db0f26a447af06a089d935b3225e36615a07babdf2177ea3fd1670",+ "xprv9s21ZrQH143K29UvdRPWan6npJHgHs7bmiduFR6qWzf5z7gFuqqcR7XoHpSBamvgTn1Qzy3JqMSuyxJEnFN5s2s9mbMna7WAiKheLFAA4x4"+ ]+ ],+ "french": [+ [+ "00000000000000000000000000000000",+ "abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abeille",+ "3bf3366c40256d7e2fca716fddf8673425c7c7e444af290ee1edf1bbf095e6e78a7190253f3e46f1e2069345d4b05ac17b242faa225c0a3e4d268976744e0698",+ "xprv9s21ZrQH143K4ZsEXSdGmcpsqn4YxjPgHqa4DFvMRmD4oTkiFuYia3srKewVsU75LN4jP6PeXPYFWYpgP8B74tQjwG1GoQ9T8eJxGDjzibF"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "implorer visage sonnette voyage véloce pourpre volaille tribunal implorer visage sonnette voyelle",+ "ab9180b7dfdde74e5cf8781e5692e2c0b55afa8bc1987fa8e14e3fb83c88b195c53e9f939f8febc33d2958f5fcd8add57843cb318d8886130ef9c9879c826357",+ "xprv9s21ZrQH143K3MCzhsD85FFZ2d8vDN2QdRTN63gbU3qHjZxn7utU1kwYGR5bxQkssS1Tji4Tuw8vpTzDWogXxhbWYDKuZveeRBe5wiNsuFJ"+ ],+ [+ "80808080808080808080808080808080",+ "indexer acompte bolide abrasif agréable dédale abusif appuyer indexer acompte bolide abolir",+ "0c1ece83a464688d74744723d609e30e191d05ab8c082cf34bb2405bc4363dbcf6a9f83707b577d230728b3943920f876ec844e86dd0d117152c23802d25be3f",+ "xprv9s21ZrQH143K36u4b8J8pxUwCCnciUPZF3JjZsq5mPCDEpNWA1j4DnYDJ53NV7b6cmNTM6Je6DG11hJw8Fq1HXKEasrpxDC8WqaEsD5BPGq"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie voter",+ "7d2f168ce71ba3e40e74baf47a072a94e49973c0dbdb33a62b3a285ab167c704a85d6ce0d15cc6a4dd3bf1311334ee0d290ae7d20115863d5f5633b8dfacf2d4",+ "xprv9s21ZrQH143K2AsqW9AAdu5C4zUaV45MgyzBQAYbEKtKusR83UzLwCZdqDwqJ59ebNryoNuVA5pEiY1eBYqr64UGkwZGezwaceCqxPWia7M"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser adéquat",+ "93d81d146eccb7c624cc25daa4cd52736d64bdc0fe020940157e73c108a87ee34d94d7e9554e02ea0f9a7ea5574426220bae7c4959c197a6c9e2318cb252683c",+ "xprv9s21ZrQH143K2iCVwUFixFL9fh5f6XrcBfaLgRzozbBgzj3dTo7fBTn4EsQ8ERgq9hTPxFf2hgE57vJDfEGKwsegQFoo2mEM6HEgFavJkQU"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "implorer visage sonnette voyage véloce pourpre volaille tribunal implorer visage sonnette voyage véloce pourpre volaille tribunal implorer vinaigre",+ "dcf42783150cdb92672c9ea7d13f145401661f10b89bfb012a803ca7713e97181ee28ac327a982060a7f8aaa6e8c649ca2c5b83c24458393fe41739ced31d987",+ "xprv9s21ZrQH143K2Lq1fbvL5nedyALbSBmmm4EbcVduaPFArtunM6azzNy96pcKbLvVBQcFFDa3Agp54eMhCBH5Ehtbxitv525n2SrhYvALPiD"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "indexer acompte bolide abrasif agréable dédale abusif appuyer indexer acompte bolide abrasif agréable dédale abusif appuyer indexer agencer",+ "b039606212ccadb0d05c7a0c08605c5137028d0253d26b9ad6ee113f9595700d9834b2eec8b224975a6d9585d7ad39e962036edcf07d5b125b0fc225d519982f",+ "xprv9s21ZrQH143K3m5hJrQxzUdU4bDDvLSisrf1Cv5HQNCEzbUokwaVa8X1huTY2JaTPJug14EbopY3gtdfWjmVoo4CHLzuwjf5Jhdjgaqsh9n"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie viande",+ "e12d20a535ef5e9e2f87e05b5261bdb51451e052fe484feb87543f5cb7a8822c4aa0152492be1259fba00a28c1e95518a90f0645bdd0eb822516d37ac881f7e0",+ "xprv9s21ZrQH143K2woSZYBmSoRYygWNGUUrkLiy2i4FmReSqyc568kb1siagkBpHFj6MzLbfuh8TaWKDckfnfphrYyKTAG2rk5y4sGv4fjn5gD"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser anaphore",+ "0f3eec3279b55f3cacdbf1aef705a086078d7eb8048e402202572e7038e9487e39104b4794e88a42192af030a176b034fa36ca6641fb8128fd23c30806b96c23",+ "xprv9s21ZrQH143K4UeephsshHgatV7uj2D2DotKpGrWx6ShwxPdRxXtyQWxnadSvjT8Df7UDxmRuiFJa92evePgqwgJSG5pY8VPJ8hNiyGHr1C"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "implorer visage sonnette voyage véloce pourpre volaille tribunal implorer visage sonnette voyage véloce pourpre volaille tribunal implorer visage sonnette voyage véloce pourpre volaille studieux",+ "8f12b35fe92a7586dfbdab9721a91300d0dbe3185d0943021667e62fd5a643e0cf2443e544738c5234009aa50faac0dbb123ac847c31dc25d875c56fe39c6186",+ "xprv9s21ZrQH143K43qySSryByKtU1Df5foSz6kDEHUsR524HZJzxnKrrU3wTdeSvHVvpitC8nAb2kdeVWGREu22KaiaMF3YjEZQdb7htpY6CXP"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "indexer acompte bolide abrasif agréable dédale abusif appuyer indexer acompte bolide abrasif agréable dédale abusif appuyer indexer acompte bolide abrasif agréable dédale abusif axiome",+ "53ab1d10dc8de3a80171b5f00495a3b49e2c5afd486f8111b1afd0ad24f43eb0aab4acab1d4c51126beea32405947924c237157b29dca69fcf64eb635708895f",+ "xprv9s21ZrQH143K31JiBbFJEViuVwAuWSD5X8PKzS4d2PoSCxHpBXpicCEMKndL8TURUZ2zeoA4vDGkECeJS9wMhrRcmW6ATmPmmwC3ziuBRdP"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie zoologie valable",+ "b5e96f552ba44ec827c1bc5ef362e8cea68dd6f36f2c8640aeb171cf9b66198fbdf155fdbcf7dc505431068f972a92442f33cda0065afc1e9a7f5f7097ea6c6a",+ "xprv9s21ZrQH143K2umcmDhRrUZ8wsZ7ACj6rvFCEuDK1coHWpo1RwYPvy3dpWmvPjstSMm9fm3igm9gjsAesjqAU6Tnejizy919FSmyofhRTyS"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "monument dépenser féroce entasser comédie ferveur optique sonnette codifier discuter dioxyde nerveux",+ "d322acd69a849cce8719674eeb7cd76520de01ea35210012a44a5dcc19faf285202c3fb3c749a46d338ad54ddd398029ee308ee352a89f65180dbd3ff750dd50",+ "xprv9s21ZrQH143K4RswQfcyMEjx6Kc5tEBR43MNp7XJH5mWushqBryZTc97VNPNVfRop62sGwu7Tg3vsd559P1G8h6M2N9j1Zj1vVn1919uvmb"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "fiasco ivoire mardi révulsif signal enlever envahir anormal vaisseau essayer céleste sagesse engager mener différer ruisseau lutter esprit",+ "3c0c90b30e1a8bd7aafda95f92fb09bae64988e2431d6c3896c8502f76203652f0db1d4640417d8d3f00ea4de59f1719513f1c01145eb8ee4b0fd73d4c4f706a",+ "xprv9s21ZrQH143K3eSdUPkhpFZamFuu35iaZqRwFWijYkE54fvUKoSNBHkXetNx56yvcJSFoeHT9y4AFuWTcFFzezPuk6D6LqnNscjFnvdGYDt"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "flatteur cultiver oisillon destrier brusque crainte oisillon labourer remède substrat parfumer banquier flèche enclave fémur sombre jongler damier insigne voguer rasage gomme crainte incendie",+ "7363c9fd3127cb683ad39697f3a7282a06f1fd1ab1ceae8e2e0d7ab2766f3b8fb29162bf46e0d6a4917a0085b763f7f6f36adfdde742b6aa4ff1973149b5d239",+ "xprv9s21ZrQH143K3YEnEhMfv1Mwoc91DekvEqXxL85fFtRKtfFk3Jsqu72bUd35jHsTA7aBShvTmhhBrHhqGZ1uVg7RTwzTpnarDUoGU8ZXQ9D"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "prélude routine négation brasier arlequin logique cuivre hiberner cirque moqueur halte barque",+ "c46b545d5e7398d0b5344ecbcc20769fb0fbf674848eef1591725a1113f5bed0edf6d78925798cf87994157f43bd9d0eb5e6f3de7959e2e88f6a586e7499b79a",+ "xprv9s21ZrQH143K4U7WubDzyDun3RrpuVsFECM9u9tNxjhJhZFCJL5DJvGhKkUAbmmhqpwUV7UvgPPG2QFoDq7jYY6Neb6MnQmQYTo3RF57ZXS"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "froid soluble horde sinistre rouge rocheux exiler causer orbite résineux renfort vaste récolter maison serrure tonique cirer bélier",+ "93856e02d3ab2e6738958350f2a96a18183c0c02aa7cf50e4e6877b1d4f9eb4be1806b034e4a4a271390b7b6ba6b4209f5e293840e93a41a2ecb16ad47936c03",+ "xprv9s21ZrQH143K2wjyyXXC3Cra6k8WJ3CPEWc1EgwEyUNjLAxGVoegrni9eMik71k4L6wWxLtqcwgD9TzKLLiaSddwLFp4F6hJ4LuDBWqVJcq"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "mouche embryon bison femme bondir renvoi louer social largeur déborder rétablir miracle adresse rivière machine époque culminer indice frégate ouvrage gourmand déposer exulter grappin",+ "08cd47b905df56e3bfbca6d1ddb7ee7ae75d45f6e5928d337bacf34754d392c7225c611136148e130dc516cdc7ade8e8a95ba62ccfdac01a107875ce3e2cefd2",+ "xprv9s21ZrQH143K3BFxuNuyFSUgQQ3f4tv6HLktHoVxvMfBKVBow3i9GqeTF9oBbG87rZBX79QTCQqgPbn3ywWc8G4ZiUdhdY3xDAciXLtE9aJ"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "brochure sextuple épisode digérer ruser affecter cantine rivière torse muscle permuter talisman",+ "8e4635efc7352a6fa18723aff498fa297c1ed1997c0f3e77a11e65155b25934cf90e74ac66d207175507887068a5c85d24b825d06a31ce75651fc9893e509869",+ "xprv9s21ZrQH143K2CpRxCahnStujg79zUscDimXKHemoY4A1Kep85cPai2e8wJ4Y5R4uhrG76merXXjSuukqjVrzJMBPsGC8apQqHymDsBPf9e"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "informer poivre capable volcan dénicher voguer offenser ruiner tragique sortir glace enduire allouer serein cirer semaine opportun abriter",+ "ed4ed89acf10eb53fd67c9f81f4e8cbe39dafe42c27e942e67559a825c6083d3373a3e98215c37318f0f28c13546895e76a080521222f6d70a9528a582dcdcef",+ "xprv9s21ZrQH143K3N7dvQS9PoT9BemrTHWfp4qzUPT7hbGdZ5uMn6RgMwKerbmrrAqjrMGUskpGJaaorTXnbgtidgEbboJVjH18qDMkLFPf36V"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "adverbe fuite jaune épaule imbiber éluder frémir adulte attentif filou fémur idylle muséum mobile bureau loyal hélium jugement péplum encadrer rédiger acier posséder pavillon",+ "81ecca7ce712963df79d6611d2510e9fa31d307557a5eeea9513a9a940c2531472fec2c6988b70f649b8a3416f8f90f5c9c8f0ac4897f4a5a1304c651226f330",+ "xprv9s21ZrQH143K2ZnKtKjoyhEcsuEzLEzq5oC8sGegz59C3vf4T8VjvRN2UQQsHw2ndFj1UMNo1uuVtxKzjmNLvVAd2SURTgkVXKykFc1S8jG"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "ultrason hublot agacer éclore englober ravin caféine abandon séduire farfelu tropical nettoyer",+ "2efa119637c044ba28eb610178d7de49dabed93fc16f5af675aa661b731567ed3ad7aeb36a04adfbfb694bbd065f6f840ab80369ec3c253ca122deb208ef9f7d",+ "xprv9s21ZrQH143K4NWbtgfxvJK1Us782vFXDdb2bTK839KWEUZFnQTXXt85hAHXML9xkKyuiaiKgD1n6ytUZdgT8hirdn4SQ4DR4T4MPiNQz3K"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "prétexte grogner instinct jongler sembler paresse papier vaillant chenille louve cynique dissiper inoculer besogne flairer jeunesse chenille cellule",+ "887a87c38340befd47d650b73849907b5892a0db26e17ab55601e4e789ae1d0dd4bc3e7fcae0fae25c3e0d3315456fe8a5d84944d2b799cb63fb9544fbd0e568",+ "xprv9s21ZrQH143K4G36f6wnN8JWRYHdbvANd8fF6c1pjb6T11LKxw3Xch5iacc9vyGQDcf51GrF7kknKumq5csscAByQiC1gaY7vFygMghGtJ4"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "vaillant chance dimanche sécable bonus séparer vecteur forcer raideur officier censurer cohésion meuble agiter prison mutation filière rincer novice solitude élargir renfort gronder tornade",+ "e59bf24814adb55cfc2399e03d94e81df4a906ca5e75f36f2e297623ffc418b8202e9b1444e0e97234e2d55e194d45f89491dc9533a1c799fbb86c5838cc3454",+ "xprv9s21ZrQH143K2gBerhfhuxyfCEXGiLW3sxY1scEJUtvroAtxPqqatzqTzDmkgxDsL7C7MMbBk1ZKBXHpYiJziBM2VFbTbA3G1qjDkTt8PU5"+ ]+ ],+ "italian": [+ [+ "00000000000000000000000000000000",+ "abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abete",+ "d2ae4bbd4efc4aba345b66dc2bfa4ea280d85810945ba4e100707694d5731c5a42ac0d0308ba9ad176966879328f1aa014fbcbeb46d671d9475c38254bf1eeb7",+ "xprv9s21ZrQH143K3ZxfinfrsmnuKNwdvRtypJ1TEs8JuE6MEmAMDwsSZApCyBFopme4iR7RnRt9XKFprfLKs9vooFuFK6h2a2hzHuXTmE9md1a"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "mimosa vita sussurro zinco vero saltare zattera ulisse mimosa vita sussurro zircone",+ "f8c609647319a50116e9b7d1a0ec5535c6d08d6c958911fd2c8b2dfd55a61e63e9c6c60c22b5c3aec725acb41980e63cb3ed75fb80648092dee1bbbeab476a6d",+ "xprv9s21ZrQH143K3yxi91AAWZvT8797G7kL34h3453QNFSiQybwss1gmx2zYCmbg4tiZdYSEsd7arPWYd5MQh28VBvcEqvXSBRu1zYajyJnzFD"+ ],+ [+ "80808080808080808080808080808080",+ "misurare afoso bravura accadere alogeno dottore acrilico arazzo misurare afoso bravura abisso",+ "4025269bc4f7550bbc3c61592944946b0d4ac855a5e4582bf86069cc0c9429455cc40d84ba215ed1cec28e27ffc88460c38b9c4e8c486ae878d7c85e95b222bf",+ "xprv9s21ZrQH143K4Hh5BqryXtMu7QLbJC7yDh9kscJ4h3PuxA382w6YyjMMkiFVyfmdYFwfP8sVWR1eLygHmczccbzH7pTGXbeqAy54fNVA13M"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zerbino",+ "24182cf43f956410b5def9df90e3db0d6f3199c2ebd26e7ddef888ee3bece9101d132e449bb9e1c23dd9ccc6131d2f649c021ee591e88cef8d17cb434ef69efb",+ "xprv9s21ZrQH143K2hiPzq8SzzER9TQDFYpnYbfg1hE8wUYwcb7JNRyM4aDB14WfoUghRrBKFGffUx5YsTZvPbdCbuVMJG7egUZVsRTJCviSror"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco agitare",+ "2161a4b869f98778b6321714e2502adb11ea120c12163b46fa34e36442ad1981b911a2f9ec82b497e7cd206fa7af2f21a94bb6e4a90159965854784e1558658b",+ "xprv9s21ZrQH143K48ZSAvHY5BjAyPXxL3pREwQPZ9DxPagzRuK8f5TcKnDr3z2MSjB58uC871CjjjsNTaUa9BxEzEwraQutT42co4mfFGusE2B"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "mimosa vita sussurro zinco vero saltare zattera ulisse mimosa vita sussurro zinco vero saltare zattera ulisse mimosa virulento",+ "d9a6205a985fde8c2337f6cc6acf77a93d6ec7dc792551c01400f5d9aaa86aa943416c99fe60be141ca27ab333d9f96648b40b266d6b2d6a6e5b07c8939568be",+ "xprv9s21ZrQH143K2wPR9TuAQcWLNSM8X9oRjUCC3GN6aGevV1zfm8cwi2JtrzS21GzAaqd2MJDszoka7xiduQrDc666Wk27qYhUTagEfr4E218"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "misurare afoso bravura accadere alogeno dottore acrilico arazzo misurare afoso bravura accadere alogeno dottore acrilico arazzo misurare allievo",+ "cfb1f800cd5a0f7a8cffb12231fc61739f5f87c963ead5e205dd48221c3417eb1173d3209d9a8ffc4f00ab291bc22c1480b4a0a4fdeef9a1f3916d0ccbed5591",+ "xprv9s21ZrQH143K2EQGrM2arQhKSEhd4DwraP86UrUKsVcLvM2NujY9rPJK78rGUCD4Z3AG8tz8brxcSaDQYDw18jZz8FuT1H6ZdwFyUQVERus"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa vile",+ "05a43b9c258f6e83f4073fe4a66d6309e94610fe12dd5d598f4725e4e85ff1fde5ff5b1e61b40e09a481a98953f9dc818342172a460e5e6d17d9ab14874447e2",+ "xprv9s21ZrQH143K39Y9dY1q4sf3Bv7dkFfDasdSbWUKNx7GCsMp1JTo8KFDRq5TNkGr1wQYm3QZaALpFhcHWhPV67oVZQFMRFu51yVxeL9KDMF"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco angelo",+ "84055239f41c182bbfe6ede6db2e8bc4a97cf86746643b7ea6910c71d67bb2a678a97ecd378cfbf59e30db720b1cfde0faaee73afd3c5deef2188e307d04442c",+ "xprv9s21ZrQH143K2b5TRb8ReAEzasjVej3ttSzyy5YRu91SdQKP4XUZtgeUipuJx6YoArxkiRSBU5eP2wu6dmgLhgBQJ8Bx5UXmwFudc423DdN"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "mimosa vita sussurro zinco vero saltare zattera ulisse mimosa vita sussurro zinco vero saltare zattera ulisse mimosa vita sussurro zinco vero saltare zattera tarpare",+ "f0e226efcd929216020a9e8f879f06b146d28fecd2856bd401a62ecc0ece8bc6ea717e3f9df523a6a00bd4ca8965e0498d63e779e3156dbf174ebac74ad7be31",+ "xprv9s21ZrQH143K2dys2Z5k2tZoxJuBENLaf3cTEpb7JUeXDp9i63vbPMqVNqHqx5bGJ5tdNug3JFWJCC4amzYLoFRi3YS55MiHnnUN6w1V57h"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "misurare afoso bravura accadere alogeno dottore acrilico arazzo misurare afoso bravura accadere alogeno dottore acrilico arazzo misurare afoso bravura accadere alogeno dottore acrilico baco",+ "ef549c1e44a7b183031b41f9f692795406de605e43ecc628911a38d7c92f392660c48313a08cf1a055a420d4a8c6b12bef7ff354c903303bc3a5dc12948ff5be",+ "xprv9s21ZrQH143K2mWU418GviWbPrYTveuukEHsYJRn8aUxV5PrE4XacaDX74amiRkH3m3Qmnz7YPRRXkF33z5472Trb39RksWRmcJitkT5uJx"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa zuppa vedetta",+ "5089f33aee7852d86a01e8afbfdc8a0ad5af51538e62e3f007d098fa4fc9817ddc990fa87b7235273798e2df52228b62738df923bc2d711fed9cc0558b3ebfec",+ "xprv9s21ZrQH143K2VqjZNjnyCMUrTw6XhLt2BsaPVpHD8MKpKq5mq9S5SLyMSv58y7nCofNGZSgQQpky6aujCURjVuAM9jCcN6xjzbeRQYYACi"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "pesista educare imballo formica curvo imbevuto raddoppio sussurro croce eppure epilogo poligono",+ "4ffd8b7879c0c6d7eee14682a26465d6429b8b921d6ea3299fb8a448d84d19b47ead5b23fd14449539cbd358abd19a23560dbd8c4bf6c153d98ea0fce7f474de",+ "xprv9s21ZrQH143K2bsiFmYu4jzf5fjcHrrEarDgY3NYhmUEU5bxz3eS3NwS924WYnioF6rf6Fij6XQdmCNjcCJwqdFJ1AKKLMxa4obLnbdFgA6"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "immolato mummia oviparo sigla stirpe fonetico fosso appetito vasca galoppo cigno solubile foderato pargolo enduro sociale ormeggio galateo",+ "188305ae9b45e400f6a3ad839061265f36e6050118283b85a3ea842aae1cca29c808978b3b0e297dbd794b74916fc43da57172e90c9fdab930638863c3472522",+ "xprv9s21ZrQH143K4RNJpMYxxAuHtA77jzNEAjh3MznXe6FejAGGUPHTDUFL3z6LB1FioxHnMirHNcHyV4QVsXs8rZACW8YSJcNo3Wqjg7dNoUC"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "infatti dire pudica elica camola deposito pudica nobile servire taverna restauro baritono inflitto flacone ilare suonare nastrare dito montato vulcano scrutinio lisca deposito mirtillo",+ "093ef04fe24f1c45148f3d4d9a54fb033638011507418cd7cbd91a8fa12157e1cbd9d095b2a660db26e8d674cbf6033a384954fdeadcd7c20cbbd3da46d90f1a",+ "xprv9s21ZrQH143K2ZBg22DZJyspAmZcVMa1efjpsz89Hmcsu7dPZ39sH5RCCNr2kBRNFQUUmcwsNUTyZ6UGk7zjZKgCdXrmxg8gJ7FBCEeE5Au"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "sarto smottato podismo burlone aria omissione dipolo marmo coricato peso malto basso",+ "d9e2a2e18ca8173859b0030186941149f630483cc9fcf3b189e5752d4f8b7dce2b285008f52ff1301dd2e2a673a4c76f8ffec9f8617fd577173b90c6af95631f",+ "xprv9s21ZrQH143K2FR5gEkuFMhmYGVyUKiSrZspfn52HonhhbvwWrn5fL7aST1yxtAfd8cQSyWVGH9TamV4SPvyKRZbSV2U7ZTurX1Zoe86ngE"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "italia sultano meccanico strappo smeraldo sipario gommone chimera raffica sforzato sfamato vendemmia segnalato oscurare staffa trio cordata benda",+ "c40130a2db00d82c2dfb127c768724c522cbf7f47b464061198c65e9bf4e3879262dd112cb7a526bf4450785e9f7f7e7511f05985d9104d9e75e1baf038c91e6",+ "xprv9s21ZrQH143K3QTUdUYC375Cvtyea83NqatsgA5z3BMp95Fbd7QfQRvzqgjuCMU9zhSgiVtAYuDoR1GK9gQRP8juxnFR2Hd4v3mNgfMySGq"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "piacere feudo bisonte ignorato brevetto sfida onorevole stufo nulla docente sfuso perbene albo sinusoide orologio fulmine diradare mitezza iride rata londra egoismo gravoso luce",+ "e384b6486328949618978c6d2607df3e7a9db9acc94ab24183aa4e7c1af0107ecbcee2dcead27d7f20acaa427d3d6eeac620ff24ae4ac2ba3b6ef01585418f25",+ "xprv9s21ZrQH143K32TYshJvJnEHVnwukKyvaQauzBPx42JYyGT1FfyUvjT5iEfYuaETkYiAUJE434hs5Qkh7FRSqK7kwy5GwjsfS4S5txFKwbL"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "calmo statuto fucsia energia sodale aliante cedibile sinusoide trovare pila rinnovo tiro",+ "5d5faba1d0db08a9f0cdb602e571a9b73565707429d2482e4fcde5a9bac1728b053c65853199fbdba73716bcb8da0616820fc817a309c99607dc56dddb34c344",+ "xprv9s21ZrQH143K2h5d8kmFvaQT4xHzXrAVwmwAMyF1p7jBg2QoE8RGGFWe1NyqcNPhy18CJ6UKcvHTXkijihhLE18yrpec1DxggywYDVWGiUo"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "modulo rubizzo cefalo zavorra economia vulcano prudente soccorso tuta svedese limitare fluente amico srotolato cordata sportivo querela accusato",+ "7f7bd54b8bf5c99a949d3ddc1d4baeec78e503f14ddd20500e307be89e940e5ead97530c014c33053a9b0c942094ea1bad649b2d23d6288dea8fcfe2e3a83c52",+ "xprv9s21ZrQH143K4AwqRHE1Swki4WW3ok7NF5cCs57wYAEhcaDhw9oVh1xwSbCeofP9GMZkTmZo6kSjj5Xh36tuuPebZhDBQRcgRnzLaSgrpL7"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "alcolico lacrima muto frigo michele fessura irrigato alce ateismo incendio ilare metallo pilifero pergamena canotto opposto manovra nemmeno rimorchio fisico selettivo aforisma sabotato riciclato",+ "197457046ab546a171b247c54bb8392aa2ee2d40f07831019776745f17aee46fe9f1611f86f9d7f0cbcacc03ce696082fc13529ba0cab0d57f76934383be0f3c",+ "xprv9s21ZrQH143K416VC54NFDGXsQeXLSAf2JaUD8jQR4SDqAFq2S3BdcSRW66avfcoRK5FsPPCuBKaShrskgoGchxdAeBzVMPFHrJkCwAWgiE"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "utopia melodia allegro evoluto folata scuola carisma abbaglio spillato guanto unificato pollice",+ "53c4c5de8a16381908e397fcb8ce5dcd8c90911d9b538afe83862468816889768d94d040bd249f4eb25d915b05b31addfa0b06d89fe15f521fbf3c8545bbb434",+ "xprv9s21ZrQH143K3u8cCFUJiaxUCKdGZ9Z2u76hUbKNMoCy51gq982aahsMbN5ArmhR1VHFJSMKLsFLkTsSX2gwjrEfj31QsDqCh3nE6FmcV4e"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "satira lusinga mordere nastrare sposo responso replica varcato colza opinione distanza erario monetario bici india narice colza cilindro",+ "5c8c80b1e440dad220a295b282fad7e8a44bfee5210d853fd52d26e8a006787ac7bf4b0a4f81d029e2ae9cdf71814f193bbb23e4b3e149d2f99b03e2417b39a0",+ "xprv9s21ZrQH143K2gMABxA98Be2P18eaj1zxU5qV6sCiMT42cuvLQ3dM2VBnEAvpFwWEDtDcbSkteNAF8nUuCyBETqpAeKmqV6niTPFEECGcLx"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "varcato codice enzima spessore brillante squillo vento insieme scoprire prugna circa cruciale peccato allusivo savio pilota inarcare simulato precluso sugo fegato sfamato lusso trono",+ "e89b83bd1a5fa859922e0045acc84cd04edeb4bf6b5352d197fbed50af0938b17bca7ab9beb8c882d0e0a67597d9e14e88c10e63b824e9206d2848fbb8a55b64",+ "xprv9s21ZrQH143K42yTQvEavGhekzrzpxHY1nspnh5qcgThCuNVAyJPWRMeznMV9FnChg5f7k51DBMawNZKWTFCLy2az1CnyGgCJ2Jk5PGb6Qv"+ ]+ ],+ "japanese": [+ [+ "00000000000000000000000000000000",+ "あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら",+ "5a6c23b5abdd5c3e1f7d77ad25ecd715647bdafb44dab324c730a76a45d7421daccee1a4ff0739715a2c56a8a9f1e527a5e3496224d91293bfcd9b5393bfff83",+ "xprv9s21ZrQH143K2TDo8AAss7eUkUqLFzBnypFpqjQUMVUrSMvrrgLiRxQPrYnhfoS9NPp3rex725rcuN8pkDL6pwqWfdPtiqa9ib1B37vZwfy"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかめ",+ "9d269b22155b3c915b09abfefd4e1104573c528f6977cde89c6a68152c3c714dc6c7e0e62f221c322f3f76e4d0bcca66c06e3d2f6a8d70d612c87dd6dee63976",+ "xprv9s21ZrQH143K3kavBMu7K49k18vjQHhNL1ciMgn7S9kDMKdyK1vEpF46UWyoXCvdBLEp8U2bhissPkC6iwXjMgRXyQ6SHbyYYGcnFqNXTW1"+ ],+ [+ "80808080808080808080808080808080",+ "そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あかちゃん",+ "17914bd3fe4b9e1224c968ec6b967fc6144a5795adbb2636a17f77da9b6b118200ad788672fd06096ca62683940523f5178f6ce3845c967cbd4ad2b3643cc660",+ "xprv9s21ZrQH143K2NAPUK7UVbLB4Dd7Hvb7fqysvFyKES5iujX4BfrwUmy1wvWJb3kBc1Zs2jxTTBxBPHuHziB1ZWWUxELrn8g8VLrbjaWDmRe"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "われる われる われる われる われる われる われる われる われる われる われる ろんぶん",+ "4bd21b75de4f262b0771a97d6fc877ee19329236ced6e974c4c81a094a5f896758033f7eae270216d727539eee3bc9ba5cad21132a1c6e41a50820e0ac928e83",+ "xprv9s21ZrQH143K43f5tXeZRJ2RMiS6nLhxcqvopg9mc84xTiX1UjLjVQDBHrwmypY45SPscFqT7zJet5b3riay95fJMM4dfZXtoBTcKKkKEao"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あらいぐま",+ "a59401a14bb821cce86ec32add8f273a3e07e9c8b1ed430d5d1a06dbf3c083ff2ffb4bb26a384b8faecb58f6cb4c07cfbf2c91108385f6773f2fefd1581926b5",+ "xprv9s21ZrQH143K3VMwP9nGq47t86uaVENmykbCRuUDKFDSfgDFjJUFuF88JfPPMzwgwFPoaJaAD3YeqrCQBFY5S2jaCXXuc5Vg7u9jb6iB6XF"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れいぎ",+ "809861f80877e3adc842b0204e401d5aeac1d16d24072f387107f9cf95b639d0a76141ab25d3dc90752472787307a7d8b1a534bea237c2bb348faac973e17488",+ "xprv9s21ZrQH143K29NaQCx2DxPkszvvbYj5FmZ7RMMAoCE932mUkfzaAYFf18JiaFC4bJujA7XAsm7TFddxdkXfn6U34te59Bp27CWp71mw55c"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら いきなり",+ "01187da93480d0369fff3fc5331284ad6a60cd3ce1f60dbec60899191afa2a2b807cd030038a93ddaf14d4f75d6de4a0e049ee58c92197eb9ca995770b558486",+ "xprv9s21ZrQH143K312KY5MRB74dWa9BDHG7rV4oK7VAfA1eVKPDddPEUTXRZ5ShDzVQJp7d8q8xQLPYVcHYVuxb7sUo6EQpJUytZRHxyytSYbV"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる りんご",+ "a1385ef66f20a905bbfc70f8be6ecfec341ff76d208e89e1a400ccea34313c99e93f4fba9c6f0729397b9002972af93179dc9dd8af7704fa3d28e656248274dc",+ "xprv9s21ZrQH143K2LmZMWVM3JxKTHZEDZg1ZEUZH6hx9yJhBWRSgGGYD8TAaPa6MYto1t1bBXgPYFMLx1Gidw8fJADdFNzvqrAcXiadUPfTTVh"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん いってい",+ "c91afc204a8b098524c5e2134bf4955b9a9ddd5d4bb78c2184bb4378a306e851b60f3e4032fc910ecb48acfb9e441dd3ceaaab9e14700b11396b94e27e8ac2da",+ "xprv9s21ZrQH143K2WD9BSegbAkGzg4XbqeY7gzCYGWaWfmRifMJJtmDo1pjXmyuEwPnKcLwTZ1uqosvTRBAcPdUUvdHxY6rKj6R28vWVWGLKuu"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかす りくつ ばいか ろせん まんきつ",+ "79aff5bc7868b9054f6c35bb3fa286c72a6931d5999c6c45a029ad31da550b71c8db72e594875e1d61788371b31a03b70fe1d9484840d403e56a1a2783bf9d7e",+ "xprv9s21ZrQH143K3ZPQiQ24sxzu5PsqazSLn1W48saAFWiDhugDPp7dKB3v5JLfMqzTbqjdwE8P2UxFSKwnFc5CpgMaH2dSmoWDRuaAbrZbJF2"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あこがれる いくぶん けいけん あたえる いよく そとづら あまど おおう あこがれる いくぶん けいけん あたえる うめる",+ "0f46c02350b3f1227c3566dea2ff0f2caf716495a95725b320a31a3058d5d62596fdb816be75909d2c5f7094beb171dc504ea8ea60f5e2e40bd8aa0d9339aab0",+ "xprv9s21ZrQH143K2TSJ2oumYNzqQGKmvehh1NKNzAjpu6Ue5yPvtzFvX8aCvBk2eTg8TJfwjiGLfA2KCiajZ1VXBvtQXqk7Wryxgps4BYyNmZt"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる われる らいう",+ "a0705c2feebefb61509dcc49c57586c35379c1981c688fc1d452da44443d9a651a374f1ad2ee3d7847b50655cf9241d7e607be436c0df7c8bac42f2a82985a79",+ "xprv9s21ZrQH143K2k4V9TkTiFB2LviDq1oSHbRha8AmnPvBtRRVAnf9WJERojPPdki6sbiuNbxv91VhhdreJnaZh29Ay892Mj6KB2aZnysqfvR"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "ておくれ げざん しねま こりる きぼう しねん ななおし ほんやく きない けむり けまり てんない",+ "b80f83f27ec3a6cbe804be0661e9bcc30583484dbbd37f689d4952bdf4ad29d9b9f5774fc4c87b733169416418b81f272a3eab37feb22f5c8f6deea6bb08f8c1",+ "xprv9s21ZrQH143K3VLugmPmzLDwfFeo8cgoZ2ajCQwWhGKfXut8C1XhdSNuAnXjz5W2bbbNPmr87yMHAaPMaUhE6MNnfGc21WRc5Lj94Tbbnnh"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "しはつ たいちょう ちめいど ひりつ ほくろ こやく こんかい いひん よろしい さくら がはく ふっかつ こまる つごう けぬき ふすま ちから さくし",+ "926ce4647a8f91552ae00efa8880ed7e43b6f8e9cf51c38851b0e242569ea96d77a19c777d28dc33d8912c3e3bc6c59f7a82b6daa25add2c39a492fdebae79da",+ "xprv9s21ZrQH143K3eVmFcDa4FcZCGeNryYBv99mdgidpSeFBt6ppQQjE6MXegs3xmT4arCDXvtg6dGpr7vFY8A9bzzeJ7E9jyzWLPDSrixwqub"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "しやくしょ くちこみ どんぶり けつじょ おとしもの くうぐん どんぶり たずさわる ひたむき みうち にほん うわさ しゃけん このよ じどう ほめる たいよう くふう そんちょう ろくが はんこ せあぶら くうぐん そっこう",+ "94308a93dc1bc12f8e917b2445581240d83cd82ad3c52f9ba4125aa6ce5490a3624fb3dfd7e22923ef7ff3b778157e8bec76392b122bf465fcc56ab4f5a73401",+ "xprv9s21ZrQH143K3ResRww4txupLREzrNjYYi6X5FKpKvFUoCwhJS1HtSn21gDkXcBYxQBd9RngafHfWqktMmLgPrVJTisEQbRn4strGawHGpQ"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "はいち ふかい てんすう おさない いろえんぴつ だんち くださる せんちょう きさらぎ てきとう せもたれ うんどう",+ "5fae92448afda85f10a50236144cee3068ce21e20a34447a9a4b6e9d5b000a4347a151d7024c2068c4d3c29c46a6f541a94b98624cc25b2c8ff42cafe6a3087b",+ "xprv9s21ZrQH143K3PD5fc3CCepnLDsenZAqyKpcbquY3BR9sfosi7rbjXMo5nL9qUb91FwYUFdN9wJt9v95YoKJJFm9V7a2Xb1sZgstQkZ54cL"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "すいえい ほとんど せんやく ほしい ふうふ ひんそう ざんしょ がちょう なにわ ひはん ひつじゅひん られつ はんぼうき ちそう ほいく めだつ きさま えがお",+ "a49ff09e55b62b8fd3d4b88b4fae2c9062e18c06105c796505fde3cc7655cfb9c922c02817d18dd40832ecd19d80a71fd62d915c34cd8d95fc55591d12b6f677",+ "xprv9s21ZrQH143K4AKun3sek391JX1qnWpxriP3eyJviCUq6ouiH3RDzJ1f7Gm6c7dgAkxn18EcaZ6CBNvVkmLxVvB1fdBdLiGeHXf69j4eCR1"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "てそう こつこつ えんちょう じてん おおや ぴっちり だんねつ ほそく たなばた くらべる ひまん ていき あんい ひんしゅ ちきん ざいげん くたびれる そなえる しんか にいがた せきむ けしょう しあさって せたい",+ "55d101db3cb8872853a3e84ec97fdeac63fdab33d92def4dc4694beff0f504da29f953bb463d9cbaf0c4d442672d40c5a58d6aed35d5fdbb2768dcc482b59bc0",+ "xprv9s21ZrQH143K2dmFtHdM2tu7mDdKwsbeU7ekYU2dsFDBt7dNbXcsLTnN2MNi3hQssGdcPgEfiXNJcgnoo76t1nAHaoyVBXZ1z6hP8L2UGTs"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "おたく ほうりつ さいかい げねつ ふせい いいだす かいてん ひんしゅ もえる てのひら ねいき むいか",+ "1b0fca340ccf977258987a793b3e61b0e20291f8c27645e74621e87c01d0e881cd601bbb0ed98388d22dea341498ed74aea32975a56bd3bdc6027196b7a21640",+ "xprv9s21ZrQH143K3Z6vsGR17YLtiGF2gTkCm9Z7XnJm1ffrLQ9ah7AVye6NyACmsWLZBoREcdCRS7tq4Hrx1NDWqHj3MctxWixcH7XBuigtJri"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "そむく のぞく かいふく ろてん げきやく ろくが ともだち ふじみ やおや まかせる すらすら こぼれる いぜん へんたい きさま へきが なたでここ あさひ",+ "d7919c8a5458fa4ff55431021938b15bf39b7542df902f371625d9f064a37b1d06a34cba920a9c8b8d6bf34514f542f5ee91b9eab81a063f9acfad932b43c62b",+ "xprv9s21ZrQH143K462YnNY39Q7Pq8ZXzCXs9k9GexnGbyjVvu3QhrNLCkPxNUtaKZBzqffnmiuU4ZiA5UQKtwVqr1WmXyM3QjVaVg8Cc93xMDQ"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "あんぜん すうじつ たいふう こんぽん そこそこ こたつ しんせいじ あんこ うしなう しまる じどう そうり てはい ていし おめでとう たんまつ せんげん たおる ぬめり このまま ひいき あまい のらねこ にんそう",+ "1003a30a516cfa1c30de2a53fe6c5936dcb8ae893f944f459ea4e1f2202716320350dc2ee5d92289dae3c5b1771fec863fbbc40146fed04d0855c6af70b0c7aa",+ "xprv9s21ZrQH143K24jAnV3a4K7xAoD486pMXSvAj41fkB61DopHqcyznUX1zmkFgEGBpNkXi6dckNcpcwXB65i71eBwP25t24QZHVRGJYURZ2Y"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "ようきゅう そあく いきおい こうつう こもじ はんだん おんしゃ あいさつ へいたく しすう ゆうびんきょく てんぷら",+ "17593d396c66d776bc15c06e5348bdfa38927daf92402c335041dc500d7c8ea9eccf4f4d1c187785b8c9128d06bd7048a1706006fab82abece74185448caa811",+ "xprv9s21ZrQH143K3P24yRmaXsA2pntRyM375Kb6cF4P73bMQNKyXUPPR86X5o83UmTi4iyQWaCL48EoJUVqC7KxsPXbrAC4nKxjMh7FZJcucRE"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "はえる せっさたくま そんみん たいよう へこむ になう にっさん よゆう きあつ だんぼう くねくね けらい そんけい えほうまき しゃうん たいむ きあつ かぶか",+ "2567b4dc469b5d1d7c4aece40e642dea3d5cebd80a577b5a4d72fc2b60da6ca657d3a01270c47530ac71f812e648bac01aaadc62c444749fddec8982430fce7c",+ "xprv9s21ZrQH143K2UfZfzss5CkDCtkRJvLN7DmM4sWjUaYMF4rfeWDERACvg7jGSSkGeFLrcHdpXUFty7wJ5yzyzcbRDqv7b9H6usZ16PnKy2c"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "よゆう かんけい けぶかい へいこう おかず べんごし りえき じゆう はんい ともる かほご きぬごし つみき いきる はかる てふだ しほう ひろう とくてん ほったん こさめ ひつじゅひん せつぞく めんどう",+ "909c8c992019adde332a11f0ebd1b0c0fbc9dd96e4d3d30ca4ecb0d06f743841cd25380f87b3a538f46dfa3fb3a5ab330487f99d128b1c6bcdbe476d3bbe2af2",+ "xprv9s21ZrQH143K3a5iyuaeKiPGQbhQLUaBhzfd7inUA5ndrmcYEc7zZzTLGM37Du2M11nXChrzXyZ8ZYtH2dG1CkWE39R749XhcKUcVs9avTR"+ ]+ ],+ "korean": [+ [+ "00000000000000000000000000000000",+ "가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가능",+ "a253d07f616223e337b6fa257632a2cc37e1ba36ff0bc7cf5a943366fa1b9ef02d6aa0333da51c17902951634b8aa81b6692a194b07f4f8c542335d73c96aad3",+ "xprv9s21ZrQH143K2EgqD4YCzE4rJ8rojXAMzqftKGS7694ApSkSoLgheNMawZYv2sArwHhLq4AaJfV6WWtGLjFWdFBT47wsjnq9v6iMXojWZ1V"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "실장 활동 큰절 흔적 형제 제대로 훈련 한글 실장 활동 큰절 흔히",+ "e6995bf885f5c64932ca28bbb00bc100a6b89cb6edc987bb05f05f99ae7caf78329029c189834c1cca938000bcf08423da011558a60cf3d90c9035eaaf241b9e",+ "xprv9s21ZrQH143K3CgBqqMtQxnP4VvSCjaTs2GfqHadwo4ddmKkvzasAHRwAsSDqjvJQu8jmAdEgBrLkVYhHdyZKWkZbyfuwPQG84PrWjEuCLQ"+ ],+ [+ "80808080808080808080808080808080",+ "실현 감소 기법 가상 걱정 무슨 가족 공간 실현 감소 기법 가득",+ "1bb52039a6cc288cf806740836002abce493724edac3d3b9458e3581427df76414b422171ef115d823a01c6b39fa68bd0fed20bf5e64dec008fcb22e4b7f26bb",+ "xprv9s21ZrQH143K3v6uaDUdZX7Cr8uC3hCE6gJ1MiGsnmRojRtUN6Y8nBiWVfMUc1skLwuUb3NshkJSdCuFew5G2y6YTDCRsSWNxYsJgZCpRxL"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 흑백",+ "b6eb986d6aaf7d0cd0eae2a667ff8bde68c8780fb5a728cf500e29119ce99c9b079a4217836879c1e73b8a85422a85b564d819699a4310a1d007b5be24c24b6d",+ "xprv9s21ZrQH143K2P5vpGbzebpVw9pZzmHHyuoTFDAFCR5UTVwQPdkuKPJB5fP4a6YgAT6seAuv9uGsMcnoyVFkVxoKZxQgB1SXjy89nHvvSHx"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 강도",+ "f40a8db48df9a7fdd73a7b3ceb45f668e4eff098f275a0a5cd739d31572c90aa92bc08b9043d0adf059a945e47e2fdbc26c89dcc15b3893a2a705e4539523ae3",+ "xprv9s21ZrQH143K4FQh3Y65u7oFeQhimhg8fjRV12vp7kYxB1EnjiBVFY1B7AdjQgLcfzK8qLjaZKFq4gfPC5pQtFtDnHi6Y988Skm2oRr758w"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "실장 활동 큰절 흔적 형제 제대로 훈련 한글 실장 활동 큰절 흔적 형제 제대로 훈련 한글 실장 환갑",+ "3162bc17e0f2f01ee571022444d2c5fbddf6a68dedfe734c319fb574592e9c0328f6526116b3b0b025b23391781d0bef8f43bc8ddc2b054b9f52e1fd6a88e3d2",+ "xprv9s21ZrQH143K2cE8CZ9tqfnmvNZcEJpwnyRRcsqDPokupVqx1hecEjQF2zrM6amGPiDiEKGZdNWrps6DdEcP83UABBjV7yJSkAFUWcP1FDF"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "실현 감소 기법 가상 걱정 무슨 가족 공간 실현 감소 기법 가상 걱정 무슨 가족 공간 실현 거액",+ "9fa92e4524e0f7412935b2deea23593c0955f9679d3285e3b955f5cdd2a659ee005ee99bd385f63d82cbdb54a3849229fc9a700e198b65a1452b511884b543eb",+ "xprv9s21ZrQH143K2XL77upQ6FjJr8a9oDGHakG1HQozxUzHJ1aVbv6JLPuX2nXdbopUnxBoKgVBHiZphPg4Krqm1xxUm6YPcZg9NeAiqT1MAcK"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 화살",+ "2543a88c8a31570dc9ee868a7b153f7f2e42700778bae7a3aba7017357e708b5cea97e0d9753c9226abc90b83c76ae369d74515ac64102c51a5fd0f809cf8b92",+ "xprv9s21ZrQH143K3AK2RE2fUZyLssNUUJPyAYjXGDGE7Ysq8hTx7MG43NG7KDALxx6TaVjXCmxFwB2wZnNtSBM2asDYC4YcDMEc9sbCyPPYjdo"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 가격 계단",+ "edb71011bc0c227103ba8a769cc36ba609e5407a771727fc0c8cba1b5a44d21ab9163d9deaa37427ccc579864e21f08d0fdd3a53a6be258d3c73b898a01ce2b2",+ "xprv9s21ZrQH143K2vZcddzTch42FzN5kcvsdtyMNR6QfEgZy1JEor4wzwFKj4JGq8kJEyC4zMQHhN7Q9mhRZJdYceTALfwxmpUAD6AtSzU3n7L"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "실장 활동 큰절 흔적 형제 제대로 훈련 한글 실장 활동 큰절 흔적 형제 제대로 훈련 한글 실장 활동 큰절 흔적 형제 제대로 훈련 통로",+ "dbd640cc9d3e99939bb0fc4473738571e314c29468f01fa85f57e296cf6e8e269d6e32434e46aaa63384930cae83728623195a932a48ccb71a9ea247720d9371",+ "xprv9s21ZrQH143K38C9hPGqNHuxsvKmNVwY2gRiRjjbzuBNme4PgfyLzkhYEt4fGm73xs6c9QDh2GxxkntMiFyeDsMDPkLDa9TkubnWZFCsJP9"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "실현 감소 기법 가상 걱정 무슨 가족 공간 실현 감소 기법 가상 걱정 무슨 가족 공간 실현 감소 기법 가상 걱정 무슨 가족 구속",+ "9a0ec04a48287ae628d61428f921de5f40fc1035f21883798e05c36f9705b2525a00ebd6bb89fcae9b8af8e9861d0083de331199d6b85b24cff598609a49b305",+ "xprv9s21ZrQH143K3PRHhckbBdnb1HMFJWXd2oPh4sXWWP8iMrH5fkxKnDucKqdWFv895Z7BRWKmbHj5gRvB6QddHeMUrk7wzSRzf7ZqkkVCipS"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 힘껏 허용",+ "340bd57209e54e8bde6ca750147933f7e44995047da87b61f64f70f26f289a377e25a65f5efb11f9e651917ec9866d54846516ae0fba956f5f536422bb47d91c",+ "xprv9s21ZrQH143K4Cr2Z2NiQPe5Xz6W92ZCE6SpW5NVmfvjtJgJUAG6WwkQ4iZ1AyfbUDb3yoiQsdoGHSSXNEMtAX3xPdtD38n5JeD8Lx7XNR9"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "원고 물질 생일 부산 마요네즈 생활 일찍 큰절 동화책 반성 반드시 의식",+ "8d148c7f8ed529d7a88fe2bc8bff574b56406f9928ab5426df793f4d3a5121c7c6974c856ad20f66ecf04fbecd3bc025912b3e41d500f1e5be896505e01d08d6",+ "xprv9s21ZrQH143K3t9XU9hQ1sEMc3LTT3MZ4Ar51RchXDvo5sjxDF7bFNaLfz7DttnvUqKAThXzv99QKccqpLQB6DdgR3NJV58AixcCdbw6ZTu"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "서비스 알코올 오로지 착각 카운터 부근 부정 고양이 허락 비디오 단맛 체온 본인 완전 바람 철학 영하 비닐",+ "3b67b06a2386240f75abe8f7905fd0fdb4cc2baa88c090eb9bca3cf144e6e33bbf3dd9085addfa52cd0ff9f2f9cd63ca69e7e77ce903ace942ec7f5b451148a2",+ "xprv9s21ZrQH143K315xG5w3u3mrnhSMVsNVdKmA9VZVXeV3fAcLX94k4N9oDEgSAxsMi3RhACEKFfgWkFex6T1uHSjSeHPxnnVF7muEAVNycgE"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "설렁탕 모범 일곱 민간 낙엽 매스컴 일곱 언어 지진 통장 잠시 국제 설문 복도 생방송 큰딸 약수 목소리 아직 횡단보도 중독 수필 매스컴 실컷",+ "06b321dd10cd2d0dec17212163c5d31f5ebda67027c0159380348d31ec5c5e7914ec75a44d4e225bbe5ce3db967e2f1ae2c9d463a638951b3e16d75ecb92cb17",+ "xprv9s21ZrQH143K4TY4YjtQLxBPerqUshALn696njp6gNVkBTYWWZ5jrQPZRuqxiMoJ6RjQpVgVZFqKBRsWnGcci6UNgPdHbRBuVn93aKdCD1n"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "제주도 처음 의견 김밥 공부 연출 모델 시장 대합실 원래 시금치 군인",+ "5e68ec0b343b62e221ede6dd5d6f33dcf8b5b4f4925ce6a30f49b17182ed0a40f7c7f3248463843f1999dd671a2e9c2abf4e5443a4e88f2bbf10b79524cab827",+ "xprv9s21ZrQH143K4B4gzTqVD1szP9igPch2u3xqQZ2gBsYe4u9kunYh5smSB3Md8EXktwjWafLwMasGuLPiSGXPCwNTgdcEPBsgtq6MiGMscje"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "소음 큰길 식당 커튼 책임 창구 사흘 다양성 일행 질병 진급 혈액 증가 예산 치약 하룻밤 대한민국 그날",+ "c8a07b4a163c3cf4ef400a96bdb7edc012dacb957326de185e66f7804e912c02329ab07520ef05dba38b2b3f6ded8a8691e1b17a38658aaddaed7ca95ff1588b",+ "xprv9s21ZrQH143K2h4eVKk7zbdHmjz5U5poyBbddPTS38xb7rjA9NMdUjYC3Dy2X7top1kVw51cFuJ2UHmFU3WGAzb5Hz4imvuz3ZKWy2R7gAP"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "월급 보너스 금고 생물 기분 진리 열매 콘서트 에어컨 몸무게 집중 우체국 강제 창고 영혼 분필 모든 심리 소나기 자랑 순서 미디어 삼십 술집",+ "683d1f6324fa54a4c4efa9b0573fae573ebc1c8b373890eb9b1e6f760f586126af2a3a39e0494f653ce6dbb954353023c304dd42d80aa939eb5a31acaaa3a60b",+ "xprv9s21ZrQH143K48a2XvjEnkvM3ry54SZdbERB51GvtK6S5hbSwV5SbBkqa2KNW7b3gwY7hC2JMgiNMD45aMhABCDvXUoZTvrq6typLaUAz6k"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "나들이 침대 분야 바이러스 첫날 개선 논문 창고 하필 윗사람 저고리 팩스",+ "f767f63c4febb5c832890f6129d0c3721555de40c28ac11093d23447f507b98f134cfef190cf0f12f1e41278fae5334f460c24c69cadc9aacc5d98efb3903f06",+ "xprv9s21ZrQH143K3K9kkX6aq8Tmqp5RjoDwbrEbqStVfNmSWBofcYA3wYA2MQsDsTZjkfkeu7gz9yJB4foKXE1mveXDf48vdA7L5mPZfC8aYdW"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "씨름 정도 놀이 훨씬 물결 횡단보도 인천 철저히 학비 킬로 수염 본격적 결심 취업 대한민국 출연 일정 가운데",+ "739584c55ab1c8053a44ca3fb50237e066590c92043cf3f45748768df65778bb79175d511543d96112f0a0e7960df081f74e6e477b953a1681cb5331de8abc3e",+ "xprv9s21ZrQH143K3XQJtWHu2YWz62N1SFBLpbweVNpCUoeFEWdukPL2rXvQ7HoDBwPWCnBc5otLQnHomemkmZ19m49YjQTqZY6uvjbDQaWpS5B"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "개구리 속담 액수 북한 실내 병아리 소망 같이 관찰 선물 생방송 신용 유난히 운반 남대문 열차 시설 양주 재판 보편적 증세 감기 정오 장미",+ "068f3943d3b3ba61b74e7900d936fcf4d73fc74852bc011e7405213edebed9f1d6b9a25db10c3ad5552b779225321a36304c757d0479e8b591655d0188961120",+ "xprv9s21ZrQH143K4ameDhGSneS8UaxoYBfQ547c6eDEBvDJJ2cqzFHCCfqcoybLTVreVRjQo6SUDKzoHeNtR9bCQY2qF3iWCJTheeDYTUSGdTs"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "해결 식초 거실 백성 볼펜 중세 냄새 가끔 출근 상인 한번 의심",+ "5f7125457857a8870d1ace1eb0f87479385d08ab8827998f57cb0cab5289d31a360310cdffaf4e8d1202a13fd8bba2ed9bc240a59b6d486d418647c55c7bca44",+ "xprv9s21ZrQH143K2fhpmAA6AkCVJWY7je61aLnS882ga5H3cMuoSp4G4SCzg47MRUjVFQYVytFdSu3M1hYyhW3xkuGqxnUXx1K6jFTg4C5gmst"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "제한 스위치 아프리카 약수 출입 잠수함 잔디 향상 당장 열정 목록 반장 아시아 그토록 선풍기 약간 당장 단순",+ "8c6f94c633c8752381e7bb207083025d7cef6c448695393fc21553e1ac269991a3ace1a2562a6129bdc34494c7a6c01d19f600da9af985eb001d71d2fb9e1480",+ "xprv9s21ZrQH143K2b5dBGtut1we5Wz9S2L2XAXfj6xftmT2omNHimaPwbTWszDvwSLfpjpQCjXKfqW827u8a4ADhCQEnTiiFj4dtbc2DZJRucn"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "향상 담배 박수 추측 기술 충분히 협력 성적 줄무늬 인체 단위 딸아이 왼손 거짓 조깅 유명 석사 참석 이야기 크림 변동 진급 스케이트 하지만",+ "0ecef71bd6f0948d9186c2786086a00f7140a00d37c836d01567077aac0dbc69f62189c02a9138dcc79a74dbb676b74aad4959fdbbf1d06a7798385f8eec97b0",+ "xprv9s21ZrQH143K3zjSpKnVTgqBrJfMK5iZXLrGb5Z4zcWKo9URusFq4n7YjnLR3RCqD8Mytzk4qjHnk52PfdQUHCCJUnKnZxqhMXGGVVe1NXZ"+ ]+ ],+ "portuguese": [+ [+ "00000000000000000000000000000000",+ "abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abater",+ "ab9742b024a1e8bd241b76f8b3a157e9d442da60277bc8f36b8b23afe163de79414fb49fd1a8dd26f4ea7f0dc965c760b3b80727557bdca61e1f0b0f069952f2",+ "xprv9s21ZrQH143K4ayH97er9xirrbGL9hEywmMfh1LdQjwtrsnrvENN7c2yKs82HypXo4GAMt4wpnna9doa1FDFYXQTSFReWDY4XAf4imN5m6s"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "imitador vinheta sogro xerife veleiro pomar volumoso tratador imitador vinheta sogro xingar",+ "298d1614ff06ae803709f5be5331135cb74e6cc77fa09e07a3e887c2e370401f9a73a409dadf58b5a5197b27ffb3fa5dd528aad9a1a8750d7669ce950ee60c2c",+ "xprv9s21ZrQH143K3H4uYufypQuRqf28rLoZpLTwjpeCiPBpcZtJpupYd2oS39oGnrHd7fPAgU5d8bfTpYd97MShp4f9fBGj5K1F9NqgZFpmbVj"+ ],+ [+ "80808080808080808080808080808080",+ "inalador acirrar barulho abotoar afivelar coruja abutre amostra inalador acirrar barulho abduzir",+ "800fd4e7691fbc3ceed246c211a38949c3607fe269a35829e40ca9d3e26515a4ebd64d8bfe9b66b49543fe9dab78bde7cb7102968ce669f55293bcc02e26ba0e",+ "xprv9s21ZrQH143K2qEuVaHnXDY1HbcQ9nMyNoi5tgGyFxfYXVxHipQ9hcmS2QitJxsNFatCZk4EYoyyNdWamAjJiMLhuimpjiWHPvJCw28EMSr"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido xeque",+ "7fb404372815ea28ef97a64249acd71a293ea0437b3dac8f7e193a10f3584e2055753cc8d6f025229f65e61318fc4e10d4017bd3cc3496f535eca3247d26acd6",+ "xprv9s21ZrQH143K3tfxkHq74PSrq4HqTjs5sHHxBpjoP26GnJ5Q8w7tEn5kmPESuqN5TQb9Lu4LysqMAcXHWNsr3DKSnvuu3vx6sK7V6Hhv2iD"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate acumular",+ "81c66b6789e8b91c169335be4436fd9736ca9c06425acd09b0525e1d6836383130f7f7d31378aaef8b7109503972f40d42f6c6b9f99765827bea762515d3404d",+ "xprv9s21ZrQH143K4LsdmZ3NinpZHGbQ6n7aQr5uV8Rv7Fq8yNPdAzsyrezon8NZ3ZCzode5jS2PXzN1C68amwDmp2bgAEF4jDG7NUbdRYCjR8r"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "imitador vinheta sogro xerife veleiro pomar volumoso tratador imitador vinheta sogro xerife veleiro pomar volumoso tratador imitador viga",+ "17040704dd985478b7d0666c7078201e3cd7d1fd1aca0d7d47c98a91ec7845500c611d987339a1d4c12bc506feb7c486eef0aa8ce679b1d184db5ca40fe8ef67",+ "xprv9s21ZrQH143K3XAGBJxF36qFzxkFaSEsNjqKvCtT3s7WfzgMBRcv8G9K2CRGXXhLYPWKSipBrd9dyLSRuTzQWbAJkhghki7Nn13QfHqCSBq"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "inalador acirrar barulho abotoar afivelar coruja abutre amostra inalador acirrar barulho abotoar afivelar coruja abutre amostra inalador afastar",+ "0f637bf3a487c26fb73f6a464f62ef1f6ca73a6ae083e220374c82881bf4ed2dafd874956ce368c4441e6269759c5864197e87421fbcdb7f6d63df17b4f7df81",+ "xprv9s21ZrQH143K25VJsZdwtDCmzZ5C2LkJyNDKrHiFfHHBEmtvn77g7qrBRGHTum98TqU7sWCGFZ8vjtcPVAfgGcV51vi2BrPR66KC7wNxb3z"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido viaduto",+ "042e40dacea1df76335445c37171e5c0fde334236abe1b5d69378548875d157968dfff5889641f16690dca9baa4d9e5fbf56e3aaf0765144ba96b819f37fd0ae",+ "xprv9s21ZrQH143K3xAXH3i9VReyK3HSyzSmN11DXikYFTA3senE3iWDCdYTFXL9JkBM7CeCS31NBBHNu2tH5T1iDWCBEu1TeWLfELH4CaGAiRR"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate alinhar",+ "8fe67c9f53a30f75513830e18f6bd0950354297a4977393fae3577363393e679cc13452bcfc9460b28a913ab8de9efc55f5901d1ba77e5eec791afd967768607",+ "xprv9s21ZrQH143K3UVmtSkznKJCHUGjkjoHkUd6dxdfo91K8sdpEVmzjyRMB3Sf2WC7WKzmZLw2cfJ43LAEENq2v1gASK4TmMUTkbWVoJaZLge"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "imitador vinheta sogro xerife veleiro pomar volumoso tratador imitador vinheta sogro xerife veleiro pomar volumoso tratador imitador vinheta sogro xerife veleiro pomar volumoso sucata",+ "feac9ad4e1a4a4399a7d57fe47bf64b404a7588eca1025abfa299365f7a75639317e2c89a94812db33405aa0213846bfd6d53dfd02743e2cf3b6984eb9fcf19f",+ "xprv9s21ZrQH143K2Syz6ZuTcKmPtPee4Xn5HMEnuZcNZ1s7hJdgjRLFJ8f4d8DnoBBTUchCBjBnjubsz3wum7zbhdp5h9hm4g6DVDo2LaHGJ2a"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "inalador acirrar barulho abotoar afivelar coruja abutre amostra inalador acirrar barulho abotoar afivelar coruja abutre amostra inalador acirrar barulho abotoar afivelar coruja abutre asilado",+ "32c8feae6a0bee33166468a770cb28459727e10f4f5ffef64977d5ef52a68ec51d832751a10c025058612ab256052cdfa9d8c5c87560de0453efe5a7d4597771",+ "xprv9s21ZrQH143K4XMzpdgrkDGxnU31ezynvtw3iBEfRB8sriHpEwyu2Gb65ao9YLcTTWTyxNh2g2AdtYmZ4wpTaAxLpJBzWY7jAvG1HyotoEt"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido zumbido validade",+ "739a6edd208d09e28fa97f8e8709aaf185e173125b1b427c04d1539173c88b78e81610a759e14f97a2038dcdc2c466a072788e3d7c88cc9bf36b96cb29510e77",+ "xprv9s21ZrQH143K45qUnDL5tCHKGpht6amXS4d278iTXWRJXthNTufwnbnvr6rU8kd4Nb5s9aBWdYFtfcdGkg4GdL91jRhGRtP8mZbjtTvpGsy"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "mexicano crosta farpa empolgar chatice fartura olaria sogro centeio defesa dedal multar",+ "1f0397e6d2aaf8d6867d648e9bc27b12a4ee1b61a47fb63c6676c153c472d708f02344ac56fd1a8e135e18cce4eef711e7e88529bd6c54b90715e9b3d9fb8467",+ "xprv9s21ZrQH143K2ynvNymKuhxopGpKrkH5skB2FqBYXP3N4goobBbXfYS8Vqhr6QAZQqxxr5caCjvSC3P3itpqqbntU9jTmmEdoM1E8Z5f1GU"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "fazenda invasor magreza repolho separado emenda enchente amassar valente enxerto cachorro roteiro eliminar marinho deboche rodovia locutor envolver",+ "1335d5a679638f63a494f1b71f6c6c448ccb0384167f3ef3ad07456f0b70f13b8f6097c1315186543a09325ca3581f329cdb66674d0f97c4474950e3aef3b9e9",+ "xprv9s21ZrQH143K4MHxCTSXTFPDppSnKcW22kjYEg7NMCcHEobe5G337nzsfnuud8asiYwnyMQMG9WkAwPW78E1otspYE4T5t2AU2cArUhhQGg"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "filho comentar obrigado cunhado biologia clone obrigado julgador rebolar sufixo pacote atingir filtrar edital fardado siri janta conhecer inibido vogal quiosque genoma clone impor",+ "ca0462775b11a28cb36c497a36da8e46c1ab618facf7640b42fd4974085e1d682a6ca660560a3b72dc602ff56e4027b53a8ffb691a96e4c827ef4e6d665565bf",+ "xprv9s21ZrQH143K4B1eibfqLNQbrVycyNgNCgQvDFtBLDYbEPtVXRBn44GPmw8fasfeCrV5tc8BFhzSnbg3pHdafof5rrXHm4XUK6EBrpoZni5"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "pote ringue muda benzer andaime levitar comando guloso carreira micro grilo atracar",+ "556b77fc49cd57f7c9c92fafedac1c8341598666721b874db50da261c7cded491c22ecd3235e508822507212698f645bb198f0bb1aecd50d22339e77619a765e",+ "xprv9s21ZrQH143K2NhKv3Pg9xyGsPMDwukoomCFE74Fk4w4K6pe8hetKjNrSWEmzPSr482zkiZ73aE5wTYY8jvLN4Wruv83p6mmxnxkF7qkUBM"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "frieza sirene hibernar setembro rigidez retalho exagero cabana oliveira refugiar recrutar vazar ramal lousa segmento tocha carpete autoria",+ "3059ded790eac1a5bb614c2a88989a4b5d7d4db20acc95f2a9eee1a5889012fa2b7ad2c0b0c5b81abe94d3e4c6c58265058fb257bb051552fe5f82fc45389132",+ "xprv9s21ZrQH143K2EN6vqxgJ2XMXNZx6GWcdcFXajpxqDfWvsrYFETHVWUEZr34sP49GinmReyfUgpakD6xT99vcV7yiAvPnf8BbEJ2PsWf8EH"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "mimado drible baioneta fantoche bastante redonda ligeiro simpatia lamber copeiro reitor mensagem adeus resumir lombo enraizar combinar inapto fosco orfanato gincana cubano exibir global",+ "f26d817eb6474c6ac3358737afd31a4b4be74ba268cb7dd0bab44f959c9b4be5ef7d65245bd0da8f21694bf380e3e4a9fe7f82777b4d59a8c738fc460b0c3210",+ "xprv9s21ZrQH143K4QArHuoZ7djwjgsdKRekuqKqFEBZbYpAQJU2a1mDMm4z8iG28oBs2kAEaGQDPjLu3kiNz3BV5Lio26Zt1z5KzEBxtSJGtCR"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "bifocal semanal enlatar debulhar roedor adquirir branco resumir tora modular patrono tangente",+ "fccd6ccf9cad7af20eaaba21e9f4fbb149907c41b5e000ce398e8956c15a5436e04091402d4851c4cc8cccf9e8a90dc8219f3104698d1022064538c6303dcdef",+ "xprv9s21ZrQH143K25Rf7o8uUBtDjSZkJ4S2Cspm3JN9USxBAXmmUYvw9n7W3qsxbkkptFhejjufMUtKx5QhCMG8Z6RjT58F9kVrRGKMQhatmtb"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "indeciso pires braveza vontade criada vogal nutrir rodeio toxina soletrar gelo elaborar ajudar sediado carpete sarjeta oficina abreviar",+ "c22c6e716250a3b98b0f342f77d4f1fe9a4eab81304fbcc9eb9f9852db769b4164daf2aecf72e6d9879713768628780d0b9900ece13c75aa1f433d48ea5c9839",+ "xprv9s21ZrQH143K4UsyxJK2nxQFmMtrqR5QUuHaK1KiZkp1LZKMqPuUCCe3xqVWDycrKjDSctxm2sPQ72MBM3yZx6tX7Jvemccncy4nw3HEjFb"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "adjetivo fugir irritado engenho iluminar dourado fralda aditivo aprovar ferrugem fardado ignorado moeda mesada bisneto linda guache jejum pastel ecologia rapel acionado pneu palmada",+ "6fc110cbcad37d6a43aadf52643ca9172f51c6fac45bf0a17e60e0fc24b5d1cffc73dd0606c427ef789f9e96c1d05af88a806005a21a4f2f93a5e41ab19fd7d6",+ "xprv9s21ZrQH143K2VBbZdEtqReQrWkiSUvySRGbPbyP9sm8vHAWDwXc1GwsBHc8LQU82gydYVQ2YCDGZKKzKPX8SaWCf2jrQRC7hVAgb2GYZuD"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "turbo hoje aeronave diagrama embargo rachar bochecha abaixo sanidade extinto tridente mundial",+ "d666e6f21c3f8934aa38e45db96ee64eb490156655c2be5d4da4359fc9b11cf9ffda5802ef0eedcc154fb790c41f50ec0cb40b4236972538d8a6e27e54115706",+ "xprv9s21ZrQH143K4CtpNWJhUqGJ7sMBw5MfYzKSNUmFABS3NwoqGtdAXUMyci5F11H9Vbx3kbiEqjDPMvTj84nMVZM9PF7u8ouNEruefbPFc9G"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "povoar gorjeta injetar janta saturar paciente ovelha vaidoso cancelar limpador confuso degelo inflamar avisar ficheiro italiano cancelar cacique",+ "cb72a39ca9d39c4283398a9995754bdff1785ccc0d96f842d748f4fe904f9dc9a963b7e89ca8070154edbc7d1efc90e1554ac4dc34646c38d6950ffaabeee350",+ "xprv9s21ZrQH143K2GgN5EwWK5f6MXbTAvTvN4uiu5pZSgb4MxBAy8VTabQfb7Dc3kp1VmEuKTWJbUtM2Hu8KpFUKWPm48ubQ5JHUHg5PeLwz2m"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "vaidoso calota decote sambar batida seda vazio flora queda nuvem cadeado certeiro matinal afetivo praxe moinho feno resgatar nervoso sintonia dobrador recrutar gorro tonel",+ "8c6d156ba11fbc606a92071e7230fda2446333510ef5f9bed4712b2d737ab43d2e06c4fb3929dfd072ccc8b9003c6bfa62d5b8fcf04396508c54215357f6f8cf",+ "xprv9s21ZrQH143K3fnoXd9pUHy7b6hH79o9bMW45Zc9486jaN1KKJVZrm1XosHw3sduFw58ADzqLZR1TvaB7enqXZoB8hyptgcijczutBxqKNB"+ ]+ ],+ "spanish": [+ [+ "00000000000000000000000000000000",+ "ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco abierto",+ "29a2ee16de47d07025de37e7d9c596869439f9bcd26a702d2bae64db2bf0f68383841c5444b5b3bd39dd720d2ebe59969e110e5955c8e6d32c6c3294fd87439b",+ "xprv9s21ZrQH143K49iYfUTNyLe6mVRHvYSg58nfiLkcSREsu5QefrsvQ9KWsMtX7SXsXwvs6J1esWdna2weySpUFZNN6qiXuHcobEMWGLfyaHG"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "ligero vista talar yogur venta queso yacer trozo ligero vista talar zafiro",+ "1580aa5d5d67057b3a0a12253c283b93921851555529d0bbe9634349d641029216f791ddce3527819d44d833a0df3500b15fd8ba4cae7ca24e1464b9167de633",+ "xprv9s21ZrQH143K27g7EMkgY2F1fuyqSEKq6n1iJCHuiUX5F3oGESmJSS6DcKW5JZ6qWWJ7x8wS1FCrd1NhRS4xCWDn9Bb1HzBuNpitD7FeYGv"+ ],+ [+ "80808080808080808080808080808080",+ "lino admitir bolero abrir álbum dejar acelga aprender lino admitir bolero abogado",+ "a89366f7f9c4bd98afca8edf1242507506562b8eb8a3a60468cafcb6f3037aba1e4d9a7497f6d49fa94aca87c95703873741441a719325af371f8eda9b59dc83",+ "xprv9s21ZrQH143K2dG5ptughpsWSbdXdRVg6ZZtF8RehVMaDNM2TJ6NZAtorTDm2EVpt5nwpsgtjgYW3GD6oP8Nk3ZhibRQFRJ4f5JoDrunY1m"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo yodo",+ "a9d1f751178872cc53fc5433e9b2a97526448adc4b824cedeadd8a127c2416481345dfbef2bfc78275f3498e40b4e8e2e00560100e543aba3f324e752f032bc9",+ "xprv9s21ZrQH143K4TU3oETVCyPLTqmC8C7zqqSR7L8JpMiR68YNhyvfEmXpRh6pP8gPghpFbvNSQCQppPDf55iNnZhT9iza6HRpTvKeLSDNFCg"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco afición",+ "6c9f21d46c56f723cd734e308f10ebf44b5b92a2e0d80fd66a2952b8d37af5219e0b93c59e1d8e63b47ac657ec2c524e5fb951d87cac824f84a3ac6264b7aaac",+ "xprv9s21ZrQH143K3AjqVTqs8gbpkEoSkCVEM6dAGvRm2TJgxTLih4TuPPSp6gheAkXpPRhG63Fb9Cr5MEto2j6hGJ4ZVPa3C1GHXUyPPwMRXCV"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "ligero vista talar yogur venta queso yacer trozo ligero vista talar yogur venta queso yacer trozo ligero violín",+ "f73b28d7e180e0a92c57276a29489c10a992c8a465ab61be0ade4708543436a682b2a3c22de57c48736ae6f29bebf3e506779c74bc1a835ad6b9f4e174126ca8",+ "xprv9s21ZrQH143K4PEMCi1dMq3ZwveC5um6cXR3tp4Z6LUGLhz4pmkaDU44UoSTiMQHv5icYPjH5EooZNorbDB7fLMDa531HHrKKnEEqCT5Tfs"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "lino admitir bolero abrir álbum dejar acelga aprender lino admitir bolero abrir álbum dejar acelga aprender lino alacrán",+ "f799e5c2782b50d0eb1d25b5f94984c5b4037ade236c6aa3b48b3df01b703d8ede5f94555f4e78f87a642a9676ba052865418c469c5739b3e93acc528fad30b7",+ "xprv9s21ZrQH143K2UeG5FGYFmkW7oTy35ZFgQ7qdR5tzBdKxGLCEfVJPPA98e2wUpfD3Eg6GS4833dFVVKafB3RNGMSZtxoWc56Yxo7PaBgYds"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo viejo",+ "2fd3964ac77c52232dc0eb2ab237fea2de9b7509005214101ecbbaeb40f34bce7735e848fca6339f76f289904c6db959fa573fc0aa607d969ac256693b4fb7af",+ "xprv9s21ZrQH143K3zwjASrAazc9EGeoVcQXA3unTmgxG9ZS7nc75inZw19oktj1y3n2Y7yetBatSN3v2UpS9ms3PvmrgQEMC4jox4ZV1ZrW6Qz"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ancla",+ "f600536eca941ed937318828e9ebab24b3b571558250e7a8342fc3cf16c458b2d7b36c36155a86cc308f7bef6d87b05d5dbe347f1a83c3dfbabd89e9c45b7883",+ "xprv9s21ZrQH143K2J842VWEPWH3ssnSvZYhRCzPoEcifAN4UgETeYkTdvnMUgnuftLyeGttvdvec8F1YJCmQz5mS956jsb2m8yDXZtnxRgiYgX"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "ligero vista talar yogur venta queso yacer trozo ligero vista talar yogur venta queso yacer trozo ligero vista talar yogur venta queso yacer teatro",+ "3d2a3aec779195f2628e800879d600cfaf2d7fcfa998657068db53906a00608fcc94fc78ceab8c97d6191389c4e468815ea0d11ffa4280c34c3cf17721a27c73",+ "xprv9s21ZrQH143K2WBRPum95TFxfz8niK5sbiDpQjyr915SjEJc99BrYoRhPuYvfzFhPwqUNAFtEdw4khqQMK4ge8EnTZZASbv7oy8t6SMzVSM"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "lino admitir bolero abrir álbum dejar acelga aprender lino admitir bolero abrir álbum dejar acelga aprender lino admitir bolero abrir álbum dejar acelga aumento",+ "dd095dddb50de059f5cb6932d529ad37dd32d40f72da3d0c7671ffc6bd967b4392fe233e5e9a4d9e5e60413160ae215e34375db85e95ccbab4fd4712f32216ab",+ "xprv9s21ZrQH143K3SQcqhHXxwZ793d4RXPpUQJQm8Bpf799AZPzicBxB87hu4Sm2DvWBqFvmxEjN3bY6EVzUxhJbHKdtV2k6u4c3ZNRFGhZDma"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo zurdo varón",+ "deea21c6902df5ef4a8efab8e14de53004c68817ea3de421cdd184f4159a6e9947376ed794c3ce67534f37f80b46674e85335555b5c53f44fdfef27991fedc0e",+ "xprv9s21ZrQH143K4TyBobPSoDLEze5gKjiTZXzaJaND1QHqmrnx6kULMJhGvQkraSHgUsjmisepryPQqTfWyM3ETLjusTsW35KumQ3w3RyusSM"+ ],+ [+ "9e885d952ad362caeb4efe34a8e91bd2",+ "obra diadema gorila farmacia colgar gorra pausa talar cocina duda dragón optar",+ "fcf6ebfc7d9eebab56ca868cbd2d5d05a6f2142ba903c52855dad4ab8c0c2cf6b4e047a2dd97cf382ae717dc18d155a45fc798e6f0a0b89971a4224e2a285701",+ "xprv9s21ZrQH143K3pxsjjkbjzEu1f9qaeGb9wTLZ39rUF4CoP125zApXELXDspSNV1Cn8vjvXKNQymm8iwSRpJr9AZyKvHTVVbg1Tq6kY48iTV"+ ],+ [+ "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",+ "gráfico madera muro rutina suelo falso favor añadir variar firma casco semana fácil neón don sección morder fingir",+ "5b48222ce814960e3b2f507ba58e96b4fa655f76060943b47c7a1396d431c570849e6f1595add9474934a72110bd3da06824428650be819f8d093e0023fccee6",+ "xprv9s21ZrQH143K3JpnkDoEPYdCo6QEBW4eUfKe1CCweKfHF5DkonVNPQRkDcYEva6pBBfGLeRt56UB6oSnhemWzbSBXCpdefk3hBDWqUAPb2E"+ ],+ [+ "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",+ "guion culebra parcela diluir buitre crecer parcela marzo roce tejado picar azafrán guitarra exilio goloso tabla mando curar loco voto reparto insecto crecer lince",+ "5dd9ecc2a8f504413ee001e4f27f25ad14533a35b3986b4ad505a9774740d0c0a6bbac6953a1ee47104357f4a5bc4acbc0f71813f9532fa667f3d3b6f2d6dd6d",+ "xprv9s21ZrQH143K4XB8BVsQ9TsrC12DWTkizmcY3X6kVqycT1pAVLhx3EHfWow3H27QV4YH8kMLum7nzjSexrAxh8tKZzHRYPPexmAaaM1KEYQ"+ ],+ [+ "c0ba5a8e914111210f2bd131f3d5e08d",+ "ración sapo opción brinco árbol mismo cueva lamer cigarro obrero júpiter azufre",+ "49b0de91db6c84527afe1bccb2525b93dbdae0306bd3ea8a1f629ea1704195d450a0a3211894c417f586fde217f024b4159a4f6ac7f5d18bb8b7bbf72c4f4d20",+ "xprv9s21ZrQH143K3W9fk6GQj1oNT2gYszJhNBhZjPmkTsR2jmoE89oRHHknYt8WLWREtopcnPs1ZWoJjV3A5QxrW5crBA5s6bWgp2DCfLUDA5Y"+ ],+ [+ "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",+ "honor tabique lata sultán sanidad salón gafas carga payaso rostro rojizo vena retrato móvil soplar trabajo cifra balde",+ "484af722d01c9cdc9ac50f3fdfeec010c7f713fb90dbfe84dae21d8215b683e660ddeec44d685faf3e653f396ef8ce0d341097c50bffcf67ea094ebb44294df4",+ "xprv9s21ZrQH143K2ouVUYDQXJpeurwoTTmfvUThuo1vRhuSXrVrKgsxAtaet1U9M5WZDpqaiLAPz17LLSuHmQG4eMMAy48Zt4AMzUwgNnEXY1Q"+ ],+ [+ "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",+ "océano eterno bestia golfo bomba ron moda sur médula danza rueda núcleo agrio salmón morir ficha cuidar linterna higiene pensar iris diario ganso jamón",+ "8cc9507c9ccafaf341a243e5b82c348e374b24c8c594131add8684cfc1e61ab51e5476a4006d4d780bd2b82e9d9581ae1af67c8845e40246d5b1110814a88088",+ "xprv9s21ZrQH143K3rpBRE8Wy6o6fZ6P6SX9ZrPCATgfgMHBHK4y9vbkj3VW19wRGR6tAeT7td2iB5bvwRXqXZ2FkBCmeCsJD4Uxf363BEuBwtx"+ ],+ [+ "23db8160a31d3e0dca3688ed941adbf3",+ "bucle sótano fibra donar seco aire campo salmón trato odio poco tierra",+ "55b603a9cd15a9769e21fd22a384d12de9afe0b9c0af0f07aee688cddd792b2863064767a6df9e8aebb4bf10d4482de07ffe6d7f7440df73f04fc544236fee06",+ "xprv9s21ZrQH143K31JznDuVnKRYfF538PE1GSe5W75rLGwpSuDFWaQrW48bSuzvkD7cPWqLHcjxi1AjQYFjzCEVkwpU8sq4XScgZigY4TQrSEk"+ ],+ [+ "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",+ "llaga pudor candil yate detalle voto papá saxofón tribu talla infiel exponer altivo sonoro cifra solapa pata abuso",+ "b63a7651d8655add895fd8a45f0fd4c0c71bd8863a8e0fd72782b2f36d43ef2fa8830ab46647afc8c437e701aed41b0bc6b2df9f11887c44457aefe2c11d413d",+ "xprv9s21ZrQH143K3CSbDbXN4ckMcyDHyLwkrQqvXL4xBRATernArEtGvZG8eFhxLbbRBZqaBnRc3ZyrN349dRxUeAozveLohZ5AWRKsDuk6XT1"+ ],+ [+ "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",+ "águila hoyo maldad fértil libertad estilo historia agudo asilo grosor goloso leopardo odisea nueve butaca molde lacio mañana plomo exento rey adicto puño piña",+ "e4df51858246fe7a1f5b7e0045704ba76ff9d2b099707ea1d8b731dc3216c3de4edc63bad0911179d818b20e2c2a4e8da9e62dac242f6369221802e25abd0ceb",+ "xprv9s21ZrQH143K381uk3B2rYMvPGDjn8oqzxK2tGqoNmB9eN2puxmZtB3BximsnfUd6u3eKP58aVTrUmKW4xA4ckETJwd6YPVa7G8nLkdBknC"+ ],+ [+ "f30f8c1da665478f49b001d94c5fc452",+ "urbe lección ajuste enero faena reptil caimán abdomen sobre genio túnel óptica",+ "f5e417f1f68c479cd3058e836ce47aaa52629ac4cb93e99e8025ab38e76a6fab56f6b5a6c1f20637bf29e108f41bca76a1a061d8f8ea40f7c0e5a15552c23ae2",+ "xprv9s21ZrQH143K3sNRU3fx6SQaMRXAAJPHtmBFpXF5MhZ6AEURc2zE2gmVexqAA6EFyTBUBfxv982cwPLR2fM81SivaJXcN2FdbmgzrFbEdqs"+ ],+ [+ "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",+ "rama jeringa logro mando soldado pezuña pésimo vampiro cerrar mojar cupón dueño llover barro guerra mambo cerrar casero",+ "0ae0e69a6ab7c290e1319018a36a7481b6969f73745db1fe56ed4b928b17458bd86e580b6925ec6b64558e4a1431b4761d0928928b689c37efad8122edd7762c",+ "xprv9s21ZrQH143K2VwsYAPzaKfUBfB8WtQXQux4e5R2cnLhipQ7zQfwWqg2Hozhrh44r1ZtMDzxVDA2PyUqEBtu9o2BidJjQ2psh8QspU8siVK"+ ],+ [+ "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",+ "vampiro célula dos simio bono sondeo vencer haz remar papel castor codo nivel alarma rapaz ofensa gripe sagaz otro tabaco esfuerzo rojizo jinete traje",+ "c87970357a0faf4ebf604d9c486726e1af8d2874d40f3ba30e5774d615c6eb7ecc6cc04d85d6be4e3e36cf4771f8e15350152351f918bf4a555a33d57f90d61c",+ "xprv9s21ZrQH143K4UfkWbEcDPVrme1ea7d9BcQR6XtJ1VJucg1haNWCKCxqkXBshF3QkTSq3HHX2V3qiZmob9bJTnnj6ny5SbvwMp26v6HwxTd"+ ]+ ],+ "russian": [+ [+ "00000000000000000000000000000000",+ "абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац авангард",+ "b4992d0492476a6f8ddef9704b1a34718b7be7df7c6977c456cdc36c1e999d9f2e61837e74ef499b828bfbce54af229f60cb15630c331df7b0e4bcb637f1718a",+ "xprv9s21ZrQH143K2giFszsMo3JcaECAq2BVNdfYzBScYQrgC5oQaEaTbJfbmBhvckkBUftJeWV7Z5FrKBYnU6NLxdQMkVJwHXaQEokWM34EqNx"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "обед эпизод уважение якорь шланг скот юстиция хутор обед эпизод уважение январь",+ "b918bd21a3d4bc9ad48fbf554c1b2170899bdfd6ec116b537d341c1225a9b2acf2f9672363be0041d73d7c648252b42032275fe8f6e185776fc71f87abdd48ae",+ "xprv9s21ZrQH143K4BUbYRa7N1sVZmEGzZF45eQr8aptdLiPLfZhz5vDSDy5B2fCfiR68g6gNRZ6PpaLxRr5AbjgbUbvbeyYMH3Aa6s1VzedHqz"+ ],+ [+ "80808080808080808080808080808080",+ "обложка антенна время агент бандит заново академия бунт обложка антенна время авария",+ "d0c56ec1f3c77a8258eff821c03751bf33c7e5a79e6077eb266d5189c637f70c44767406e0cf45f155e1776d54f8d38d44713e79b8be7962beb987be49656922",+ "xprv9s21ZrQH143K4RsygSRYGV6FTZGUF7762ZihgxxLwJYuj1ZEbk2co5LJ32ZaGZxauCmLhjYGvwpQj4TXiZXBZnsgmZB42QjdDj5YXKsCbLm"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик яйцо",+ "57ca8a8f55680bffef79708cbd65140021b7e221b2846befb056d90883cc3e9c074b841289742e521a85354c18ae7cb7315b1aa7bde6dd9d1ec5b584e60d9d43",+ "xprv9s21ZrQH143K2HeiRMaLyPjxTxXhUU1hTeCBdfm9TGvct4gqtupxDQ3LsMUDm4uwS61UxqkU73WBB6nmbkNzKWDQpfjXE7SaCGdtcatLBJK"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац аргумент",+ "5c4f8e125883d9f11d9b6f8bb9ab55ded2a5282df1c8bc4daa46bd7a6649bfecf2b6e9560a336db719534d0c57609baca1d49bc522280d2044791736bfb1a171",+ "xprv9s21ZrQH143K2YdWwtmHMfsmGuPrmbRcXWeEj8yM5A6vi8x5nkCcEh7sHQn2PK6FvGq8sjii9WUhvMcPPPEvygriynjHVD8Y6AEdqChjX5f"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "обед эпизод уважение якорь шланг скот юстиция хутор обед эпизод уважение якорь шланг скот юстиция хутор обед экран",+ "5d2f8d26d13deaf9b18dcdd757c02d18aa180e8ab5c02a1b977a3ac57996f59aa7338d44ffb740ace2122750f99c1da2a236c60a113882e93d99175a36521bbd",+ "xprv9s21ZrQH143K3DAEtUMX7ZRKfT2mGkE231EFFYrtnsRBLgqqKy8DXWtU7Q4vnvCgU1EWc3AQDkz27fpW2MDrzGmkuVNTtD9eHFEDmBSu34Y"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "обложка антенна время агент бандит заново академия бунт обложка антенна время агент бандит заново академия бунт обложка бактерия",+ "46031966bcb1de4496e85fb3cabbf538e0319b4621498e9dd49e77d271e5dd9dba8ada21a20a1ca2037225efa3bb69e256fe59f48186acaa8dc70730bbd401db",+ "xprv9s21ZrQH143K4TAkYZVDxfRTcK1ev8turDKM86xykq7qgeAt77MHkqA7Gsf671T1mJnu4Vq3iuDa2tW6infg3cBz3G9WbHA92PjdGGT3CLa"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик шутить",+ "ed0f6a5dd6a5585a5a76fb2dcdff3075beb0f65ac9b91012f331b17e1efc9d4d8221f1a9f99c7662c07e742ccc9d77ed24c5d1c131bdad99dc1e9c67a02cd9f1",+ "xprv9s21ZrQH143K3XeENAs3xCce1rx93vXYrMms6mCig5j8YFcXxPPoFHGevpcRaMs6sdCFS6QBHSKD6FKZMsXeusjTCCD7FFGbDEd47pjmNyq"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац абзац большой",+ "320ad7214b5174a3b49bf7525a5c4bb6de96ff2f10b73c6f30130ba66fb4bdad9676ae540a56ffd08ea405d13a3a8e7cc26285da3d8d6bfff36fe202fbe7575d",+ "xprv9s21ZrQH143K48cthZX5fnGtX8agLdraF3zKwcfo56TTHNg34r33U9VTaPZ9MHGB5y5uQRbxBkELM2BgndH9VQwXC9St7kLUmKTSHq5MUu7"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "обед эпизод уважение якорь шланг скот юстиция хутор обед эпизод уважение якорь шланг скот юстиция хутор обед эпизод уважение якорь шланг скот юстиция улыбка",+ "a14eaf06487df7ebc49779f1393dda273fce4be5799e8b9ac8550687e4111c6825fd49c920a42c0947f80c6e3a3ed2ef7e75af7de43591d01641606b4d80691d",+ "xprv9s21ZrQH143K4VvixRuJc3AgrdUNB5kG91kzA8UdjknRSKTS7JNr9w14NAWuQZcQGjmThGi8MiKNAe1bXGiE4TLuxiT2HKXhGZ7XbW5vwcz"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "обложка антенна время агент бандит заново академия бунт обложка антенна время агент бандит заново академия бунт обложка антенна время агент бандит заново академия вздох",+ "4d8ff971ca6f1f5294dd3153de7176c5634f1c70f8445072c79f8c5e6ba36a9729ac2831b53a5babe23f0e1dd0180437bdc559e6c44d338162be0930074c5ed3",+ "xprv9s21ZrQH143K2h71y4su2ffL4NQR8ZdsZ21WPKHdsH9YjTgBMAWaqQxcR4WXUBt5NSRqfqbn7N6UP44cUct6FPhAkCb8dwnUJHcyKFDCUjW"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик ящик шанс",+ "421c7e6f3365b41ed491814e2c4724f4ee09761857e4f7e5a03f67b6e29a32e4af774b3ef8bbbf3bb192b2906ad8319f88645031180956f38c9c295e6d8940d3",+ "xprv9s21ZrQH143K4HrLURDc1BhtYRSDs3cQDRo9yv4sMPHurQLcXo6JQ9A4hXmZbZ7Jz7XMkrAq4AN7iTyeifMGsevRKiFurNWc7NGTNGjTrbV"+ ],+ [+ "bb54aac4b89dc868ba37d9cc21b2cece",+ "сесть пугать роспись монета трогать директор физика экран степень бояться рюкзак порыв",+ "4156db1656dfece6891335740f11dd29d5b9edcb896e2728db9ea8c58ba13e3400d3559a9e5dc031b0c40acec2f1a8d98c3cc0280d6b6782e0b5fcace23b9298",+ "xprv9s21ZrQH143K31Y2PwmSdNGeyfKT3iGy9dbQ7xdNoqcEJykKCGZw2zQFNugPaqqpbL4G8QSAPajyYGn2KPLReU7F6LZrbABLG8pww1CVSYC"+ ],+ [+ "9f09b43ceb7e57a0ea8766221624d01b086464359164e7a0",+ "поцелуй калитка будка тесный условие супруг ракета химия вывод роспись динамика должен оптимизм дефект терзать грудь факел атомный",+ "f467318ffc53cf0b7d81fb6ee12a120f50e58568215a395691912c3be11eac41864a9d101bd2ea61759732ae0ab99059271e9a4484fbafe9429297dacd9d9b40",+ "xprv9s21ZrQH143K3Y4mrcPNQ6umuqdkXr1hZLbAyti18xGkyuSUd3xqP2t5PdEVrAuobwiuPfGF7dqzQxfq5gjAmiAMvuASg2ohUQV9RmavcV7"+ ],+ [+ "06aadd75179d6d5c5e19fde90cf9b4838622421e57a12862e1811b4cdab215dc",+ "аукцион комиссия круг дача терять репортаж натура залп философ лягушка мечтать бабочка легенда вспышка негодяй чистый глупый вечно куртка досада стоянка корабль компания палуба",+ "bd4593253ad99e8aaffd90d517c33b75b0aede40006f4f60f1cf302aeba27e5aedcf50d856f369c59e64dd696ae1127748b29067f9f46dc23528dae65fb22e48",+ "xprv9s21ZrQH143K2Q7td5VztniaTJ1CH13BFYZRSEJFatqUo3bzmEYG1Ls8YNY3k45Z2o5144jtxP9zgpKf88vCpbaemKQ7FUUnNpr36LjhKqA"+ ],+ [+ "934f1cecb1c2236ab4866d6245f7c8db",+ "пешком небо жареный лестница выгнать сарай сыграть стоянка легкий двигать чашка свой",+ "24a97ab44257cfa0ba99a0417e755d5470d97f376795e4393dad4298febea8b3e44f15ed8c4e1526bd2b520cf77116d29bf52479bd15ab8995849c3821b085c4",+ "xprv9s21ZrQH143K4c7VXaAyRDpTKoxkNGaPzmaYd6CYcXvptVYieNuSkdv3WLvkkki5Uy8TsL31oSkcc5ZU9kFZqAuYhvb6G7iqnVCnrwh68iR"+ ],+ [+ "815171aac963d551f0a4140f62df9da1ceb7739ce1c2c249",+ "оборона оттого медаль петух забрать развитие смелый объект будущее вечер фабрика звонить фуражка семья мужество дружба слюна интуиция",+ "15620497c365d1411d731172309a186e9ac3e585a57c5e3d0d6238139d22c52c6a3e83e3014ed7b60b3e1773920ae55a5238b10bf83aa6adcd6b9fa6fb85316b",+ "xprv9s21ZrQH143K2J7CmPPcF7JqzphYz5XBVVnbZ53cwkkfQPQCFotjgPosemTxgyF3rCDVzYYNVKLYxqRp4Ftz6Jp8bN2moAeTVUzeUSNrs7w"+ ],+ [+ "8d79c52beb01af2b8cfbc74713c164e33a7c9a1afb6cb6a88d344e16fe97084c",+ "падать струя иногда тепло внешний плоский дилер небо избить после корень соленый пятно кавалер менять миграция сверху злой кавалер помидор могучий интуиция встреча почва",+ "e57e5f3e9e5d8048817c57ab4893379834e6387fe3f8e546d8ee50b3c3184bbfbbd60255e3fb0fc19465f6f11bfca5db58174bed2002ae5be0fe9c4ad30db848",+ "xprv9s21ZrQH143K2jsuXtR8fxubVRgj7oebLhyTi7hHXAmLHfqKSJ8ebm93G2WhFp7ctDi7yyEXGrv5o6cC11MDZ128jbsBVbu59T8YwBSVhTd"+ ],+ [+ "4bee29faea0a596253797215e1eccf9d",+ "ирония монитор нуль тарелка пузырь роспись камин давно вершина будущее дилер жадный",+ "659aa5e40fcc2d2bda5feb4df9b4b237c8fcb0daaba65ee58dfc4776bd71907816d3ed2811eab37952a9c677b5ea603125d29d8e1797efe1ce39665ce379bf47",+ "xprv9s21ZrQH143K2we3aRVJzfB8Hp58inWapdTbW44A4jCy4kvT2b1t4FfHFkAKo5zNHozyvPxwgmPD6Whc3pXmdzxXKsS7GK9zs4z2S9jYrrK"+ ],+ [+ "a00cdab43456290799635064362851cf7d1edc74d4a8ecf2",+ "практика лучший резной мало лежать одежда ловить матч лидер рост вексель похожий съезд миля такси пласт хирург паук",+ "23affb7f9a5f64ab2b5fbba57df4d280b1f0222a8cb6dbcd9ea240ea37bd088d956ff3e712bf6ebb328185e29259e399076da5667852c84e5f9f5183de92a157",+ "xprv9s21ZrQH143K4a31ef7tRvwRY6f8zttbGLfnjTzJRssfEvjFjxDA7kMEk9R8zQYDyYU2LNo3fcyT2DmKAhufNi1i5g44cTgKUM6Qfu3SVHF"+ ],+ [+ "6325252245b88bc10e007d1e3acb8e924f03d499f8be13a5061c2d1bbfdfd7cb",+ "лента гнилой изредка оттенок отвлечь угроза дрожать букет волчий терзать унижать вялый цветок форма магазин взнос ведро глоток орган котел сигнал хутор культура наглый",+ "baf6c06e3c917c623ef26b7750ec63d85017d60e6e4d8c7e49ec53dcf6a4d6bf606f2fa55e3dcd860b6adf58033d21fc58b093a6dcf24e7604dcfcf0844329b1",+ "xprv9s21ZrQH143K32a1ZKWu8NpmX55JAejQmV5tUgDDL52dkfZzTNXYF2qyKfUkZSjrLJicaiLMTUvjxpvUU55HjwUQFuf1gB2LSJHMcjhovXC"+ ],+ [+ "f25806fe5e6125d9602d50d57129725f",+ "человек слабый скосить симфония ванная хвост обмен ракета темнота отец кричать скорый",+ "f38baf015700f685d1c1f26f9ab52db8700b27ee640f1ba2bde81149dace30415d4411795323eb61cd30d07d149dc0ee3ea33d99e4f52ee0235489d52b9f04d8",+ "xprv9s21ZrQH143K2JUoSW56YcVVgrPj3FP31H8jCX6aSimFw8xmgWFG8dtcKf5AVsxtRMibDde1q6CNCzjMSvetMGp6ywHSYW4QKJKuJJM3r9n"+ ],+ [+ "df0ad99c35a4901afbbba60cb8a96e340d1fcdcb13cc0014",+ "тысяча комедия льгота мелкий изящный ботинок хребет мыться болтать совесть кредит магия съезд учесть грош неделя абзац профиль",+ "36f72aef7f6af7f2a687fba3a5e785ab171f6e08f7b7f077e24c4b8b49ac6e026bec1402031a128d11d02a9681711ca2b8ef09d34ae770a1577335d1b59a3b67",+ "xprv9s21ZrQH143K3647uZ91pci7dmAb9tbDYmNmntYV51kDRd5Qg4odoJd4jYh5qAWGAZXvkjaJGGuYawysxeUeYy35QfUrDLD1WemPa5d9uza"+ ],+ [+ "dee92aa4e56d2debb9be104a4a3774d153456787c5f7ad23bf6e53f1892b8ff7",+ "туча икра раздел ссора сюрприз шагать учесть случай инвалид кепка трубка приют директор рядом восемь скелет рейс вокзал трепет пятый видимо плоский замок хаос",+ "79f63bcebeb97e95586fb6814b8be4781df00139917075fdf22018f61f547e1b66add4b63264326ec2f46542bcf748104c92b4f9f73705fe8769c0b3897eeb88",+ "xprv9s21ZrQH143K3qEhb6NQP35AFEguHn4iAVEJnfAVgd8pmRQVZnATZwnNACGmeg73XFU6szbt1LkrNgyfLkgJGNtdXLYE4aNfgzpvFssQcAj"+ ]+ ],+ "turkish": [+ [+ "00000000000000000000000000000000",+ "abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abdal",+ "4c14199a1b8df442bb96586e3c3d43305884a05e643fd89ba9d0a18775d80d4a91be22719f0e6ad42a7ad293872f6af429377e4b35863435b4647c617ab4ab4c",+ "xprv9s21ZrQH143K2ugGhLVYL5dCJkVLLSqfQqgYH6WNRVxqurmbLMR16tC7FY6HSH3fir8R7yx5LsAyKtbdqSrXj23ZZicU75g389hf4QJrvUY"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "meziyet yüzyıl tosbağa zindan yoğurt silecek zılgıt vokal meziyet yüzyıl tosbağa zirzop",+ "823d592ed10291fc25837c58bc122b2b37c0d20ea67530349270bcdb92e4892e13a7443eca530e9544694c8fb91d2fb7e8c29caeb598f627f90858e9fda408a0",+ "xprv9s21ZrQH143K3yg9nmyHAJvPDjBzGrWW2r8hqsGGTRrhVGEqvj4dWzwDfX9r9Pj8QWrCmnkdSgtAHJzxfxwRy9GnZvhfsHq2fGGwpDRLd3o"+ ],+ [+ "80808080808080808080808080808080",+ "milat aidat cazibe absorbe alperen fıldır adezyon astsubay milat aidat cazibe abdest",+ "8529df63b2fb01db07df4f519d1e0c9066326ee1b7d699499af8a980de371c06e54a33d8788f19811c1b25c326cef643ba4bdd23c790e259a6e109b01e440e0d",+ "xprv9s21ZrQH143K3BV2UQMgVeVKsHWDof76rfWSWtDMvYy6HcCkTzVujez6vEL9o2zCXPAy52nQqU8uyRxmZu21AbRrqyqJe2S7N4aLXmSrTPc"+ ],+ [+ "ffffffffffffffffffffffffffffffff",+ "zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zihniyet",+ "f154669700ee53ef119f7bcbbe62b2bec8296109a4359228e9d6200bf39b33eff7ed443ed92ee28af3504a31d92d12a595eda36f6dd2ab34bb2916ed5a9beaa7",+ "xprv9s21ZrQH143K4Z5Li8i6VfgWhfh5Yf5myQgwD2XQn21pMzn3LdrAQHEXe1WUrpnsfWr661XzBefNRanUfQ1uLgC9GTGiDZzkX1HU2b2Eu3M"+ ],+ [+ "000000000000000000000000000000000000000000000000",+ "abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur akdeniz",+ "c11c692fc712c924732c0ea93d1eb38de8e53c591cc1117fc3f957046b9518c7e8eeb0bd156b1c23031557b712f9b6ba4f59b831ab2f35a35e06bca5d1584631",+ "xprv9s21ZrQH143K4U9eG6arRqDzwPxtvPY97cKPPtvsBuYXg3bQFS8sKvxf6z3PLRjER5d11XJaH9J4f9aBcP32QeNsEg2MucvdHQAdf1r6zCW"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "meziyet yüzyıl tosbağa zindan yoğurt silecek zılgıt vokal meziyet yüzyıl tosbağa zindan yoğurt silecek zılgıt vokal meziyet yürek",+ "7d070c17f6421b76ea23f91cda3373f6b87f137643d8117007ca9046c523f61a0f5b824fae61d76ba34505d3a06532c41279957a695b535651702df7688bb765",+ "xprv9s21ZrQH143K3ESh22bdZvgwnjv1rfUBhhqW7MmFe3gCvgssaQMEWZmw3wJasgMbfyjtZ9PtbM4c18Tdw9EFmuVHaWRdE9zmKP1tBnxQZ6G"+ ],+ [+ "808080808080808080808080808080808080808080808080",+ "milat aidat cazibe absorbe alperen fıldır adezyon astsubay milat aidat cazibe absorbe alperen fıldır adezyon astsubay milat alıngan",+ "67eda05205e79bb0aea25640c85dec7a6e090df121113cfb8528df87288144a777a12365fc5ea26d78dc4f063894fe1f5692c1cd20b6528cb2dd1fff4aed8c74",+ "xprv9s21ZrQH143K4UxdxPUHqDx1TbUa5ymqrGT4LpxPTokXfjvWg1LwJ6kPrFjtnWQ6AJ4LhMgGJvjpfr93GgkCdsY4bCFveCAqjoG7aL1PMkB"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffff",+ "zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre yukarı",+ "527598540e10421e68a749ef0f2c6f8c874aff96d5aa885703bdd324dc691bd5edc5aa055adf5582017e32315e8c7d2a059c77928e15acce814765295fe442ad",+ "xprv9s21ZrQH143K3oTbjcF7aPkGaGbu1XWBbJmpFWRe6u4m871jzcuxe9UJwuTfPPDqy8sahH6pX3choSBAtJsKYKw9fQAQy6gykuBgGDXH4Af"+ ],+ [+ "0000000000000000000000000000000000000000000000000000000000000000",+ "abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur abajur araba",+ "28c310afd7368bcd8c0b0b85f2ea8fdaabde393b603f0ef4c457f6b14d8ec2a2b9520dbaf775686d75e786ffd016a6373c956e3a96d726c6f1a5e604c14b8941",+ "xprv9s21ZrQH143K2AWncG5V4X2SYmfNGNTJadWjzCgiwhfS9QVvAu8RHAvtMnQX7KGFvdZwuPMSjuwycA51FDezqbvEDVuAQtwewnzSbYvZvkC"+ ],+ [+ "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",+ "meziyet yüzyıl tosbağa zindan yoğurt silecek zılgıt vokal meziyet yüzyıl tosbağa zindan yoğurt silecek zılgıt vokal meziyet yüzyıl tosbağa zindan yoğurt silecek zılgıt tuzlu",+ "b37520e13cbd00a4f3f1d9bed02d6dd01668f51204d2cddbf0fb28815567882d79daf30b3971c55ac96ea4b35569f05f9bb4d827de366b4304bdb2c23d595be0",+ "xprv9s21ZrQH143K38tD64zHfNMTLQyszcLiZfsEqx3PKotFEusJJpAzV1LNGrDkKQ2kZyGUcNYkpwJET6xRihooefwYnU8sDc1AVEB8CAmYbSS"+ ],+ [+ "8080808080808080808080808080808080808080808080808080808080808080",+ "milat aidat cazibe absorbe alperen fıldır adezyon astsubay milat aidat cazibe absorbe alperen fıldır adezyon astsubay milat aidat cazibe absorbe alperen fıldır adezyon benzer",+ "5d92ee9efcd9cc3f19b8644dc72741caebf4c15d77aec6ab066d5ac3544b942616b567bbdc5eb25b20072d7082cdd7692c369d7575db53fbcdb4be2a8ae0b097",+ "xprv9s21ZrQH143K4TFEM9Dy2iDjmdDijpn3K1HRV5WMfpoaS3Hyt8GNoC3RRDb4MZTKfcckmmazJ5YLtYuxXCM3TZZvrg3uG5ptxrabr6cbbT3"+ ],+ [+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre zümre yeniçeri",+ "2822919551b434114e51a2c00824cdc2dbaf1b567bb71d56c3e388ffedccd5a4054d7f71fc4149ed73f376fc9fad629bc40d2af89f4d61b6292153b73ee5a54d",+ "xprv9s21ZrQH143K2YKCt9cmi7pXRoXpX2AjXR5ydny2pDSbVueGZdwSNN7B1HnTQRf5RtZMtnFghHgH9PRCVAeZUHA4o8wb3anoyAjvtURAB4h"+ ],+ [+ "bb54aac4b89dc868ba37d9cc21b2cece",+ "sentez pirinç rota lamba tıknaz edat üçbudak yürek şelale arena saçma parsel",+ "15a94bd53a53df03882bfe746ec24f5349dd8d7c3de8884b032b6cac37cb9771526e2b656e94137b190399e70e6b7e7d08219aa1ba965e58e1c139533362b2a6",+ "xprv9s21ZrQH143K2qNy54d9ktxsVXsrtgmW7nePreUeHV7nTvc2hm5Pyu16xzWrAUE3XAZjnD6rtLALGJRrqWgknVdGbL6TQ72fArknZe9xxCn"+ ],+ [+ "9f09b43ceb7e57a0ea8766221624d01b086464359164e7a0",+ "patlıcan havuç asfalt tekdüze ulema taktik pusula varyemez cilt rota ecnebi ekose müstesna duvak teftiş derman uyuşma akustik",+ "f04f5a4067b689cee057321f6bf400a25d96f4be59559d09446c1669595d6b856f79495609d2a1f933ab5f128f3d84cbbe8a471b613fd1e6354383c57ae0cd74",+ "xprv9s21ZrQH143K2M1c94zNSatufZPKm7r9ZvbQBb5XsTv32QbNanqRhY1FcPGXpvFtKu12gzFxaicf8i4x9aRVymXtfBMYWm4SBC7kqx4c2gg"+ ],+ [+ "06aadd75179d6d5c5e19fde90cf9b4838622421e57a12862e1811b4cdab215dc",+ "albüm iletişim kahkaha divriği tehlike rehin masmavi fesih üçkağıt kolye kupkuru aleni kenar cepken mavra yatak çuval belge karmaşa elastik şirin irfan ilkokul odun",+ "29cbaa235e2205e406ebe495999f850b309ef442d2a091a2b750d5431bf5c6d308e1a4852172c9558430049b6036ccfed3789da2367e70efe1a8bd701208435c",+ "xprv9s21ZrQH143K3uxUFEhXpHvTZhgDnmDs18GkJkswLP27qQZTDUVF8PiNieqwRR5uCsX2y7ZkMKgi1THaHgeR7q4VcMxHcNRvGeavrrzpzJ2"+ ],+ [+ "934f1cecb1c2236ab4866d6245f7c8db",+ "otopark materyal eski kılavuz cisim sakız tapan şirin kerkenez doğalgaz yanıt sarkık",+ "325bf129f10c4b4fc66663958fb09bae1df60a8dd37d219cfbf8c776891889d0e918b1dbe66bf78191687694897bc94a2b2c1b84ab1865eef60a2c03438afa29",+ "xprv9s21ZrQH143K35KwmVQGSrd7X58mMCqKqpSLRSMYnTWzdYTDgPKJnHz6A7PXBvCLJj4vXVfmSNKwraCYm6Cwqd2uGk7J8TLDchDUQMPUjG3"+ ],+ [+ "815171aac963d551f0a4140f62df9da1ceb7739ce1c2c249",+ "minyon nilüfer köprücük otoban ezber prim somut morötesi asil bekle uykusuz gariban üzüm sefer lütfen empati solfej hafız",+ "42839d70ad42ad51716b6c30ad29cb7a256fc6e9c6865470e9b302a3bec99be6754714d647870ee02cc75b6e2b46f11c4d4a63009b1ae44aba3f4c1015b846e9",+ "xprv9s21ZrQH143K3tRHyXhqsWfrZ7JT11sMZyYiwkC5Eo4WvRPXLCEHRYP6BcisHLWKNdwQywHet8Fpbzz4t44Db5TcTEYZrpUKZY4iTpLsbrx"+ ],+ [+ "8d79c52beb01af2b8cfbc74713c164e33a7c9a1afb6cb6a88d344e16fe97084c",+ "nüfus şöhret güzel tedarik boğaz ölümsüz ecel materyal göçebe pasaport irmik suhulet portakal hapşırık kudurma kurdele sanatçı gemi hapşırık pansuman külçe hafız cerrah patolog",+ "05d2850b6d5b32ca9f9d8345a40362d6f3be8fcc65dc77542e8ca50efaf940ee11cf91594c64c7b511def83fcd5ef3eb4b8c8d6ae3cb10b946f1aa2c0a1f12dd",+ "xprv9s21ZrQH143K4GVKptAzKSVqEsJiG4NpdudPYfqNxBMRhdt4Bf7xXoCeRorRc5pD9VvdgbMNQZHJrDPPCiZSuQUQ2WpufLN2JD1Z5pD9JNf"+ ],+ [+ "4bee29faea0a596253797215e1eccf9d",+ "hakan lansman metre taşınmaz pist rota haysiyet dinozor başlık asil ecel ertesi",+ "332a44ae607176fbf85941af7bcd9685b84c2a3d200058e82e099e7b20b799c2619bc85a6c4115be91ed3796546b9a9a1364f862ae156d0f923b96f21d37fcff",+ "xprv9s21ZrQH143K2d1M8F9uPvNEd5QfuvGPCJE8XBh6wf96Arz6CEzs1Hag7WFaUedyzvMXT7HQEN67V2k5RYEo2tc7hVqPpTxaEYDNLzHhPPy"+ ],+ [+ "a00cdab43456290799635064362851cf7d1edc74d4a8ecf2",+ "peçete koçbaşı rasyonel konak kesirli musluk kızak koşul kırsal rozet bakteri patika tansiyon kurultay tarumar öğrenci vasıta olay",+ "feece1c9aba98740471e6025b53f6e071815f2302b5d9cdb1796f791cb27dc5d0149c9dd6f7cc1178bfb471fe622eed7f67b85519a609cf336bc986e18c8cbad",+ "xprv9s21ZrQH143K4WQDYJVHrxMNZPzGACWvtgSEN82CCAtE91pJwMrGCnHbqRAb1YUKk4AsZMmy1vPZbDEAV7v81LZMKp75LyAgVbwE8YhuaWF"+ ],+ [+ "6325252245b88bc10e007d1e3acb8e924f03d499f8be13a5061c2d1bbfdfd7cb",+ "keyfiyet dalga gövde nicel nasıl transfer emisyon askı burun teftiş tünel çayhane vurma üslup kombine berjer bağlantı çubuk müzik işlem seremoni vokal kanun mahrum",+ "6e51f41ff64b1cf5b464e1ee4d65346652fbc455db859faec1adffe64f1c3e0153bf223a514591a31f7a35f2c1edf8112d01b488c347771cb689791b0c5fca47",+ "xprv9s21ZrQH143K36WkoNrtpfR2W31M9zfoS9b6m8TiVArdY59tzh6RraoaRj6Nid9kcPG1eZWKvjAosoKHziqBUtpkNR4X73JwoXjcK9mJGQc"+ ],+ [+ "f25806fe5e6125d9602d50d57129725f",+ "yankı simit silah serüven avukat varsayım milli pusula tazminat nazik kadın sihirbaz",+ "6ec0dea6dae649330517c17ba129179d1f5ec5ae7ed0d4a6f05caa8aff580c88404bbfd55ccd1f4e7408bc75fa3490aaf4523cd853679cfb82eb2797c7302be5",+ "xprv9s21ZrQH143K26t84vvKN1EqXrpVzKCJmv2CkZQVKBXGVdLoCW1bdFKeVTbTxbhMHRfv7S5qc9Z8cLURAW5HtVa2WvLFNLH3M6tHRr8EpfE"+ ],+ [+ "df0ad99c35a4901afbbba60cb8a96e340d1fcdcb13cc0014",+ "toplum ileri koğuş köşegen gözleme arefe vestiyer mağdur apse sprey jakoben komedyen tansiyon uyarı dere mayhoş abajur pırasa",+ "e584247a087153923e5b00bf14d179714f151d1064e74dc6dd19905fc37d7bb6fa2fd7757d6d3ca157e01fe517671693ab514eed7d449d160cbbbd0d90ac3468",+ "xprv9s21ZrQH143K2vRvkt63VCpuem6vXLt1pqtVBm7YSuxZbm4G2LihmCfAcbKXJc9E5LnEKngDHqo7oF2YDB17EBFQrYFgxJaqmcyTJbREMct"+ ],+ [+ "dee92aa4e56d2debb9be104a4a3774d153456787c5f7ad23bf6e53f1892b8ff7",+ "topaç grup profil şaşırma tarım yengeç uyarı sohbet gündüz hudut tırmanış pervane edat sade bütçe sırtüstü razı burçak teşrif posa beyin ölümsüz festival vakıf",+ "bbdb82c9d5e188f792a6ffa189b3f0fda5aa289f918adf34817918891510e312d351f945e74da91855d1432369344a3094c523d129bad0520951aab542e07b75",+ "xprv9s21ZrQH143K3opfxeLxFyAKr1ufr9qK8crqeht2XaHqsnZ6FbtoovHsocVDjfWXA9PS44CS4NkZ76CKCQhhw489dx9RAN9JXQbj3Ta5UpT"+ ]+ ]+}
+ data/bip39_wordlist.txt view
@@ -0,0 +1,2048 @@+abandon+ability+able+about+above+absent+absorb+abstract+absurd+abuse+access+accident+account+accuse+achieve+acid+acoustic+acquire+across+act+action+actor+actress+actual+adapt+add+addict+address+adjust+admit+adult+advance+advice+aerobic+affair+afford+afraid+again+age+agent+agree+ahead+aim+air+airport+aisle+alarm+album+alcohol+alert+alien+all+alley+allow+almost+alone+alpha+already+also+alter+always+amateur+amazing+among+amount+amused+analyst+anchor+ancient+anger+angle+angry+animal+ankle+announce+annual+another+answer+antenna+antique+anxiety+any+apart+apology+appear+apple+approve+april+arch+arctic+area+arena+argue+arm+armed+armor+army+around+arrange+arrest+arrive+arrow+art+artefact+artist+artwork+ask+aspect+assault+asset+assist+assume+asthma+athlete+atom+attack+attend+attitude+attract+auction+audit+august+aunt+author+auto+autumn+average+avocado+avoid+awake+aware+away+awesome+awful+awkward+axis+baby+bachelor+bacon+badge+bag+balance+balcony+ball+bamboo+banana+banner+bar+barely+bargain+barrel+base+basic+basket+battle+beach+bean+beauty+because+become+beef+before+begin+behave+behind+believe+below+belt+bench+benefit+best+betray+better+between+beyond+bicycle+bid+bike+bind+biology+bird+birth+bitter+black+blade+blame+blanket+blast+bleak+bless+blind+blood+blossom+blouse+blue+blur+blush+board+boat+body+boil+bomb+bone+bonus+book+boost+border+boring+borrow+boss+bottom+bounce+box+boy+bracket+brain+brand+brass+brave+bread+breeze+brick+bridge+brief+bright+bring+brisk+broccoli+broken+bronze+broom+brother+brown+brush+bubble+buddy+budget+buffalo+build+bulb+bulk+bullet+bundle+bunker+burden+burger+burst+bus+business+busy+butter+buyer+buzz+cabbage+cabin+cable+cactus+cage+cake+call+calm+camera+camp+can+canal+cancel+candy+cannon+canoe+canvas+canyon+capable+capital+captain+car+carbon+card+cargo+carpet+carry+cart+case+cash+casino+castle+casual+cat+catalog+catch+category+cattle+caught+cause+caution+cave+ceiling+celery+cement+census+century+cereal+certain+chair+chalk+champion+change+chaos+chapter+charge+chase+chat+cheap+check+cheese+chef+cherry+chest+chicken+chief+child+chimney+choice+choose+chronic+chuckle+chunk+churn+cigar+cinnamon+circle+citizen+city+civil+claim+clap+clarify+claw+clay+clean+clerk+clever+click+client+cliff+climb+clinic+clip+clock+clog+close+cloth+cloud+clown+club+clump+cluster+clutch+coach+coast+coconut+code+coffee+coil+coin+collect+color+column+combine+come+comfort+comic+common+company+concert+conduct+confirm+congress+connect+consider+control+convince+cook+cool+copper+copy+coral+core+corn+correct+cost+cotton+couch+country+couple+course+cousin+cover+coyote+crack+cradle+craft+cram+crane+crash+crater+crawl+crazy+cream+credit+creek+crew+cricket+crime+crisp+critic+crop+cross+crouch+crowd+crucial+cruel+cruise+crumble+crunch+crush+cry+crystal+cube+culture+cup+cupboard+curious+current+curtain+curve+cushion+custom+cute+cycle+dad+damage+damp+dance+danger+daring+dash+daughter+dawn+day+deal+debate+debris+decade+december+decide+decline+decorate+decrease+deer+defense+define+defy+degree+delay+deliver+demand+demise+denial+dentist+deny+depart+depend+deposit+depth+deputy+derive+describe+desert+design+desk+despair+destroy+detail+detect+develop+device+devote+diagram+dial+diamond+diary+dice+diesel+diet+differ+digital+dignity+dilemma+dinner+dinosaur+direct+dirt+disagree+discover+disease+dish+dismiss+disorder+display+distance+divert+divide+divorce+dizzy+doctor+document+dog+doll+dolphin+domain+donate+donkey+donor+door+dose+double+dove+draft+dragon+drama+drastic+draw+dream+dress+drift+drill+drink+drip+drive+drop+drum+dry+duck+dumb+dune+during+dust+dutch+duty+dwarf+dynamic+eager+eagle+early+earn+earth+easily+east+easy+echo+ecology+economy+edge+edit+educate+effort+egg+eight+either+elbow+elder+electric+elegant+element+elephant+elevator+elite+else+embark+embody+embrace+emerge+emotion+employ+empower+empty+enable+enact+end+endless+endorse+enemy+energy+enforce+engage+engine+enhance+enjoy+enlist+enough+enrich+enroll+ensure+enter+entire+entry+envelope+episode+equal+equip+era+erase+erode+erosion+error+erupt+escape+essay+essence+estate+eternal+ethics+evidence+evil+evoke+evolve+exact+example+excess+exchange+excite+exclude+excuse+execute+exercise+exhaust+exhibit+exile+exist+exit+exotic+expand+expect+expire+explain+expose+express+extend+extra+eye+eyebrow+fabric+face+faculty+fade+faint+faith+fall+false+fame+family+famous+fan+fancy+fantasy+farm+fashion+fat+fatal+father+fatigue+fault+favorite+feature+february+federal+fee+feed+feel+female+fence+festival+fetch+fever+few+fiber+fiction+field+figure+file+film+filter+final+find+fine+finger+finish+fire+firm+first+fiscal+fish+fit+fitness+fix+flag+flame+flash+flat+flavor+flee+flight+flip+float+flock+floor+flower+fluid+flush+fly+foam+focus+fog+foil+fold+follow+food+foot+force+forest+forget+fork+fortune+forum+forward+fossil+foster+found+fox+fragile+frame+frequent+fresh+friend+fringe+frog+front+frost+frown+frozen+fruit+fuel+fun+funny+furnace+fury+future+gadget+gain+galaxy+gallery+game+gap+garage+garbage+garden+garlic+garment+gas+gasp+gate+gather+gauge+gaze+general+genius+genre+gentle+genuine+gesture+ghost+giant+gift+giggle+ginger+giraffe+girl+give+glad+glance+glare+glass+glide+glimpse+globe+gloom+glory+glove+glow+glue+goat+goddess+gold+good+goose+gorilla+gospel+gossip+govern+gown+grab+grace+grain+grant+grape+grass+gravity+great+green+grid+grief+grit+grocery+group+grow+grunt+guard+guess+guide+guilt+guitar+gun+gym+habit+hair+half+hammer+hamster+hand+happy+harbor+hard+harsh+harvest+hat+have+hawk+hazard+head+health+heart+heavy+hedgehog+height+hello+helmet+help+hen+hero+hidden+high+hill+hint+hip+hire+history+hobby+hockey+hold+hole+holiday+hollow+home+honey+hood+hope+horn+horror+horse+hospital+host+hotel+hour+hover+hub+huge+human+humble+humor+hundred+hungry+hunt+hurdle+hurry+hurt+husband+hybrid+ice+icon+idea+identify+idle+ignore+ill+illegal+illness+image+imitate+immense+immune+impact+impose+improve+impulse+inch+include+income+increase+index+indicate+indoor+industry+infant+inflict+inform+inhale+inherit+initial+inject+injury+inmate+inner+innocent+input+inquiry+insane+insect+inside+inspire+install+intact+interest+into+invest+invite+involve+iron+island+isolate+issue+item+ivory+jacket+jaguar+jar+jazz+jealous+jeans+jelly+jewel+job+join+joke+journey+joy+judge+juice+jump+jungle+junior+junk+just+kangaroo+keen+keep+ketchup+key+kick+kid+kidney+kind+kingdom+kiss+kit+kitchen+kite+kitten+kiwi+knee+knife+knock+know+lab+label+labor+ladder+lady+lake+lamp+language+laptop+large+later+latin+laugh+laundry+lava+law+lawn+lawsuit+layer+lazy+leader+leaf+learn+leave+lecture+left+leg+legal+legend+leisure+lemon+lend+length+lens+leopard+lesson+letter+level+liar+liberty+library+license+life+lift+light+like+limb+limit+link+lion+liquid+list+little+live+lizard+load+loan+lobster+local+lock+logic+lonely+long+loop+lottery+loud+lounge+love+loyal+lucky+luggage+lumber+lunar+lunch+luxury+lyrics+machine+mad+magic+magnet+maid+mail+main+major+make+mammal+man+manage+mandate+mango+mansion+manual+maple+marble+march+margin+marine+market+marriage+mask+mass+master+match+material+math+matrix+matter+maximum+maze+meadow+mean+measure+meat+mechanic+medal+media+melody+melt+member+memory+mention+menu+mercy+merge+merit+merry+mesh+message+metal+method+middle+midnight+milk+million+mimic+mind+minimum+minor+minute+miracle+mirror+misery+miss+mistake+mix+mixed+mixture+mobile+model+modify+mom+moment+monitor+monkey+monster+month+moon+moral+more+morning+mosquito+mother+motion+motor+mountain+mouse+move+movie+much+muffin+mule+multiply+muscle+museum+mushroom+music+must+mutual+myself+mystery+myth+naive+name+napkin+narrow+nasty+nation+nature+near+neck+need+negative+neglect+neither+nephew+nerve+nest+net+network+neutral+never+news+next+nice+night+noble+noise+nominee+noodle+normal+north+nose+notable+note+nothing+notice+novel+now+nuclear+number+nurse+nut+oak+obey+object+oblige+obscure+observe+obtain+obvious+occur+ocean+october+odor+off+offer+office+often+oil+okay+old+olive+olympic+omit+once+one+onion+online+only+open+opera+opinion+oppose+option+orange+orbit+orchard+order+ordinary+organ+orient+original+orphan+ostrich+other+outdoor+outer+output+outside+oval+oven+over+own+owner+oxygen+oyster+ozone+pact+paddle+page+pair+palace+palm+panda+panel+panic+panther+paper+parade+parent+park+parrot+party+pass+patch+path+patient+patrol+pattern+pause+pave+payment+peace+peanut+pear+peasant+pelican+pen+penalty+pencil+people+pepper+perfect+permit+person+pet+phone+photo+phrase+physical+piano+picnic+picture+piece+pig+pigeon+pill+pilot+pink+pioneer+pipe+pistol+pitch+pizza+place+planet+plastic+plate+play+please+pledge+pluck+plug+plunge+poem+poet+point+polar+pole+police+pond+pony+pool+popular+portion+position+possible+post+potato+pottery+poverty+powder+power+practice+praise+predict+prefer+prepare+present+pretty+prevent+price+pride+primary+print+priority+prison+private+prize+problem+process+produce+profit+program+project+promote+proof+property+prosper+protect+proud+provide+public+pudding+pull+pulp+pulse+pumpkin+punch+pupil+puppy+purchase+purity+purpose+purse+push+put+puzzle+pyramid+quality+quantum+quarter+question+quick+quit+quiz+quote+rabbit+raccoon+race+rack+radar+radio+rail+rain+raise+rally+ramp+ranch+random+range+rapid+rare+rate+rather+raven+raw+razor+ready+real+reason+rebel+rebuild+recall+receive+recipe+record+recycle+reduce+reflect+reform+refuse+region+regret+regular+reject+relax+release+relief+rely+remain+remember+remind+remove+render+renew+rent+reopen+repair+repeat+replace+report+require+rescue+resemble+resist+resource+response+result+retire+retreat+return+reunion+reveal+review+reward+rhythm+rib+ribbon+rice+rich+ride+ridge+rifle+right+rigid+ring+riot+ripple+risk+ritual+rival+river+road+roast+robot+robust+rocket+romance+roof+rookie+room+rose+rotate+rough+round+route+royal+rubber+rude+rug+rule+run+runway+rural+sad+saddle+sadness+safe+sail+salad+salmon+salon+salt+salute+same+sample+sand+satisfy+satoshi+sauce+sausage+save+say+scale+scan+scare+scatter+scene+scheme+school+science+scissors+scorpion+scout+scrap+screen+script+scrub+sea+search+season+seat+second+secret+section+security+seed+seek+segment+select+sell+seminar+senior+sense+sentence+series+service+session+settle+setup+seven+shadow+shaft+shallow+share+shed+shell+sheriff+shield+shift+shine+ship+shiver+shock+shoe+shoot+shop+short+shoulder+shove+shrimp+shrug+shuffle+shy+sibling+sick+side+siege+sight+sign+silent+silk+silly+silver+similar+simple+since+sing+siren+sister+situate+six+size+skate+sketch+ski+skill+skin+skirt+skull+slab+slam+sleep+slender+slice+slide+slight+slim+slogan+slot+slow+slush+small+smart+smile+smoke+smooth+snack+snake+snap+sniff+snow+soap+soccer+social+sock+soda+soft+solar+soldier+solid+solution+solve+someone+song+soon+sorry+sort+soul+sound+soup+source+south+space+spare+spatial+spawn+speak+special+speed+spell+spend+sphere+spice+spider+spike+spin+spirit+split+spoil+sponsor+spoon+sport+spot+spray+spread+spring+spy+square+squeeze+squirrel+stable+stadium+staff+stage+stairs+stamp+stand+start+state+stay+steak+steel+stem+step+stereo+stick+still+sting+stock+stomach+stone+stool+story+stove+strategy+street+strike+strong+struggle+student+stuff+stumble+style+subject+submit+subway+success+such+sudden+suffer+sugar+suggest+suit+summer+sun+sunny+sunset+super+supply+supreme+sure+surface+surge+surprise+surround+survey+suspect+sustain+swallow+swamp+swap+swarm+swear+sweet+swift+swim+swing+switch+sword+symbol+symptom+syrup+system+table+tackle+tag+tail+talent+talk+tank+tape+target+task+taste+tattoo+taxi+teach+team+tell+ten+tenant+tennis+tent+term+test+text+thank+that+theme+then+theory+there+they+thing+this+thought+three+thrive+throw+thumb+thunder+ticket+tide+tiger+tilt+timber+time+tiny+tip+tired+tissue+title+toast+tobacco+today+toddler+toe+together+toilet+token+tomato+tomorrow+tone+tongue+tonight+tool+tooth+top+topic+topple+torch+tornado+tortoise+toss+total+tourist+toward+tower+town+toy+track+trade+traffic+tragic+train+transfer+trap+trash+travel+tray+treat+tree+trend+trial+tribe+trick+trigger+trim+trip+trophy+trouble+truck+true+truly+trumpet+trust+truth+try+tube+tuition+tumble+tuna+tunnel+turkey+turn+turtle+twelve+twenty+twice+twin+twist+two+type+typical+ugly+umbrella+unable+unaware+uncle+uncover+under+undo+unfair+unfold+unhappy+uniform+unique+unit+universe+unknown+unlock+until+unusual+unveil+update+upgrade+uphold+upon+upper+upset+urban+urge+usage+use+used+useful+useless+usual+utility+vacant+vacuum+vague+valid+valley+valve+van+vanish+vapor+various+vast+vault+vehicle+velvet+vendor+venture+venue+verb+verify+version+very+vessel+veteran+viable+vibrant+vicious+victory+video+view+village+vintage+violin+virtual+virus+visa+visit+visual+vital+vivid+vocal+voice+void+volcano+volume+vote+voyage+wage+wagon+wait+walk+wall+walnut+want+warfare+warm+warrior+wash+wasp+waste+water+wave+way+wealth+weapon+wear+weasel+weather+web+wedding+weekend+weird+welcome+west+wet+whale+what+wheat+wheel+when+where+whip+whisper+wide+width+wife+wild+will+win+window+wine+wing+wink+winner+winter+wire+wisdom+wise+wish+witness+wolf+woman+wonder+wood+wool+word+work+world+worry+worth+wrap+wreck+wrestle+wrist+write+wrong+yard+year+yellow+you+young+youth+zebra+zero+zone+zoo
+ data/core_signed_txs.json view
@@ -0,0 +1,685 @@+{+ "description": "Transactions signed by Bitcoin Core 32.1 with signrawtransactionwithkey on regtest, one for each kind of coin and hash type. Each spends two coins of the same output script and amount. Core's testmempoolaccept allowed every one of them.",+ "source": "Made for the security fixes of November 2026 with a private regtest node. The keys are the 32-byte secrets 0x11.., 0x22.. and 0x33.., known to everyone.",+ "vectors": [+ {+ "kind": "p2pk-compressed",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07300000000484730440220643d26f9a430fd0a9bc7c9bbae25c48442e54fbf25abdf7d8631a799e27db3c6022029bf0cd7a9d90468e545748396bc16224d9a67912a50ef1e62c26a01ac0bbdff01ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073010000004847304402204786ca72263b747fd1531026ee967923aa7a6931c12e32732405b46ca7150bef02202af3d40735df4980756e062ec713aa8381896bca4d41c4f65987f5fd1e94ed6201ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-compressed",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730200000048473044022073b8d78fad1eebfca7b5490d01a5f4cea387ac451e3dd1e5e918f6220faf20380220015bd0ba9d766c7dc5eb65648bc573602e083e8f0b24751bf49082a07073493302ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073030000004847304402200e300e56ef26ea4275ef6bc32cfa45b75ec64dec993e3e6f8040a9f17751faa30220638d665c29a1da36282160e9cc8882cdd5cc44edba2b87e3afe1f006ad1c29b502ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-compressed",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073040000004847304402202952761335904340762c47804e07514789e9eea91faa92faf689b3d90c5470f9022076178226eea8e927b3e2f173453b84849acc5ea2b9fcda471e6065822c30e0a203ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730500000048473044022055c26c7f912637a8c0cc2ccab9d3f65cf5f4b44b4052cf7bbd2be4d3ed79323e022077de50ae02778c36e6ce3ff5f30db5dcc135fa5554541e714e6f4c3774c81ae203ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-compressed",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730600000048473044022038561f7dd14d726d0ed5f0acf6c04aa2151f373bb7f45c62fd11dc10f57c50a5022045ffaf6e101aa07beab4e144aadb28e6ac6196e7c7453356078f8aba43ec383a81ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073070000004847304402201813c2bbfa383aba74cc69c9d000dc18691bbe5b58c4b4a82a37946b59e1435702204f24d61c548bde3e53e347e82055ddd7508f610dadee16b830f69e107671d2ec81ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-compressed",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073080000004847304402204c49f01694b6c6fea876ba6115611a5846849297908272cf916aff72194f208e02203a185a172a7971d75eb4546df6ecd422f2faafb2812db9218d81efc480244a9382ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073090000004847304402203a5426014db1be5bd73472d389f4bf8caee79204cb69f112a4011a7c5974886402200aa90afd76185553b6ee96883976aefe05ac579d089a99b5691dd88ae202730a82ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-compressed",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730a000000484730440220754b0548462928ec2bae61626573898604be001b5877a37cccbdc5de04cba5b2022007b01993c9d6dc2362f7cb253d26d4374cece627295baa0f76935f0e4356837c83ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730b0000004847304402202d8df54d5a95714a62b646a43f8df261bce9f5712a26a621f9ac019f637225e502200d4aa3ab38c081d2bcbcbc2180859c6bb50f8afe51ffd5909dc845e6a1347b4d83ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-uncompressed",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730c00000048473044022068fef9ce783d54b669410ca99002714d2d95b55e9bfaaed19578b8156403564f022007b1f929406d7338e4bb15c0d4ed3f2bd286613b387825f73b6aa556ca0b25f901ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730d00000048473044022062cec0f5a0e9d5133e80632c4693ad15876f487d13fad823125d736a8aae573702200a9560155686835a1fbc829667a9b8307b91ae439edd74712eca1f64037bb0ea01ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "4104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278aac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-uncompressed",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730e0000004847304402202bee0fb674bd8d709db322507489d6377fb3a17f22f49f220c3e5c8d47c672bc02205abd6f213b3ecbbe9344df0d6c42afca43dc70c84d393afbf19f86047db9e43a02ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0730f0000004847304402200a14096d9b9088be20905475009843ec22e5f52424416196b7367a1c0221af110220134c932d912b4afdb7d13e4c23240d53e4cb3fa674ab9b882d3fc436ba746a6a02ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "4104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278aac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-uncompressed",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073100000004847304402206fe56b1fba2358416d9faa474a2844ad5453ca2874d538b5b96c6525a0ee499f022067d18f1282abd511f12818b7d65c10ccbfdfab547e39c35556dbcd6f702cde3203ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731100000048473044022020c6274a5547e0b1ea5caba8cab40bbc91875d597e34e42b2ef858bc77be422d022025353e6ae87206968ff5e166ad2c70f468986a97da076ca98d6815f4eebda5be03ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "4104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278aac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-uncompressed",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073120000004847304402207d321a4df8d061bdfcb9fbd4fae8adfe78a93e490ea6eede0555d7b5e2f6be4c022021a9ea0baa9f872a51683b0998958a28da3b9545fa9960098ed6b162d1f411d181ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731300000048473044022033aabd672b4612e57f5ddcbd9c2fb2a9120a40b4f299d1264438f7ce893aa1fd02202a123a03ce84edd57ad1a5b91faa08f7327cb82dc55101cec6e71b5f3b9b982b81ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "4104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278aac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-uncompressed",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073140000004847304402201f568b867689accb45893d505ec820d0b49eeeb087e9cbc1c020174f18f46507022046771ccda5fb1369ede99b22a2afe4e0c3831b01210faaa3c381cea77d04c18882ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073150000004847304402203be325305d690ec647f16d2d9ea4bedcdf676bf27bf03cb7e7c54e7ffe365bae02203acec8ef0260abfbeb7bf4d428ee2105c437e4785c06127ba3a59b72076dd19582ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "4104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278aac",+ "amount": 2000000+ },+ {+ "kind": "p2pk-uncompressed",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731600000048473044022057bb1b6dd542818a217568c41ee256e27220ceab71f33596b347e414a7a6399802204823491cb16977216af42fd07fef24f7f16db05f585fa4dfb4512ee3499f4cdc83ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073170000004847304402204c7a648da539efe4db2974365930130fd9c1821b2e207630891f37736d95ced202205318a908848fe67912ab422d97a7bbdf19ca6456684ec0c8438983151d1baa7a83ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "4104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278aac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-compressed",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073180000006a47304402207ed9289733d485fa73afd0c45bc56070f3e83e91cf4fd8c56a407dde94b436ad02201ad10498f9d272af45c07420c4d1264d6a25cc3e5bab7586a42e9791f9cd7eeb0121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073190000006a47304402202e30a90562e01630c9cb57249cfdd0aaa7a09dc0fc45328d2a0b00eda7ecf5d9022024745a9b0d01c8012d7312d05e4f5b52ef9371ac320e021e686a1e15f77512260121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914fc7250a211deddc70ee5a2738de5f07817351cef88ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-compressed",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731a0000006a47304402203eddbcbbfd639eb49b9d2cc5be52666ed39fc912128b5344064abe35f49c3ed70220524bde923666e4320f7c35bddf60431fd752804158a2b061fd908b3cfd0b544d0221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731b0000006a4730440220076678b4d37b29d777c417c1386a10abad484d3596e47e199e59d62ad9e3098d02201d766370fb41f9d65bb965645d78fa787c9893742927b5029ee28843acf5f8e80221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914fc7250a211deddc70ee5a2738de5f07817351cef88ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-compressed",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731c0000006a47304402203cc02ab2709050a6d3c0d3d9e9e3a983bfba8fa1150107642186c26a15c32c7d02201b48a13fda28b76dabacf958f89e9391ef94b43b9acb8b12a48a926b81f17df50321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731d0000006a47304402205bba2d93d2ea11aa506531e259094456ea6f6e3c9e7e108173ba43b704bcb7bf0220117df00ae3927534f4297737b167610b5697bace27450b626a09bccbe475ee940321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914fc7250a211deddc70ee5a2738de5f07817351cef88ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-compressed",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731e0000006a47304402200ee0168d9f1c3ed6ea29ba724fbc44bb39f762fb68b68724dd796cb31bb2a1c002200f03f0fdcdacec498568539cb6c9b6ba74bcb2da211afd313b3ee20f8f05dfae8121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0731f0000006a473044022016e8e767ba5873d4b034221445d534ae296691a3e4300090e1016c4593b55cea022076bc7eecfbd841862ce26f844c3d55201f237769c31394a98c4eac1fb58a07568121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914fc7250a211deddc70ee5a2738de5f07817351cef88ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-compressed",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073200000006a47304402202be93681f318cd7361def5b35bf57e25bec974bd267d93d04eb98ea28cd0e369022028483f27d337d0a8ab383eb7df342cfe7ad2b41ad47bcf977997995a7910830f8221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073210000006a473044022008091895c85876698b8aee121ee2fac2eb6bef609d76dac62181430ef8cbefdf022039f6cc40719029a56132760ac4229cc0831f136188c43722ca65c635631cd7c88221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914fc7250a211deddc70ee5a2738de5f07817351cef88ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-compressed",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073220000006a47304402205e809145de419097b07a3d8387533dd2bcbdad123732be7602e0070007da74bf02201dad97619593c2c464784b87e3439c22f58de27d89e69a2c6320def8abd903968321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073230000006a473044022026ca60c8c08adae24cea586f9bc960db2f51f42cfd767aef687e135cd88a9afc022042171b6d4334680d4200739648cc907a465dd39bd75ad9a8e3ca4866c94b1cdf8321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914fc7250a211deddc70ee5a2738de5f07817351cef88ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-uncompressed",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073240000008a47304402203aeff029012ee314590c90a22cd490dd1e626530f403155e66182d4bf60055bc022015e5ba5c025a4cb78bb2921c80d937f4e4d9c756d3fbcf092eb1c5a5f55f0749014104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073250000008a4730440220178b67fede61e0573edae1de64a71faa610cba5412a7e0f9043a4794d50bf3c902201b4cd0cee70ecf32df682717840f26122f3859ead69f689b6dd198a7f04fe46b014104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914b256082b934fe782adbacaafeadfca64c52a538488ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-uncompressed",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073260000008a4730440220380bbe32cb389a2dc8412ebe1189ea03f5273b6aac0fa682cfcdee433e9bccff02204154a88b46ac26795e22e3db73e0c0d25ec4d8643ea8ae137a49481bea591453024104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073270000008a47304402201d0f8988ea1f7a6b5f155041de7f548b3dd55a46ac406de9dc9727e87b7a7db902205ac5e54883d8af5f7042c60f35047b96506fe52636dafa973df52804dcf64852024104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914b256082b934fe782adbacaafeadfca64c52a538488ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-uncompressed",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073280000008a4730440220607104ca1dcf1bea360556a9684d2f131c4d70591b3fcb03ed70943244375a2302204b92bea0aab2639c19630b030c0d36f9084b706617a4dca0788071b46fab9c9b034104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073290000008a473044022054460ef9eb3afda0319abfd184ad86cc1fa70505f8dd583eb673e7b5474f68cb0220150933a10acdfad72c2cda1916de955b8e2140fee6b1910c48546cbaadc9b031034104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914b256082b934fe782adbacaafeadfca64c52a538488ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-uncompressed",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0732a0000008a4730440220216e20e43c80fe509c8cd3311645e641035f29f15e4f68b2b31b77ceb029e6be0220703c774a30fb20857cc2bd4d7bea104eb9d4cced1cbd64c9755e307af7a4bd27814104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0732b0000008a473044022030c0faf9c0eddebe5749863ecdca9ba6d6177f6aeac0498e3f51c39369b40d3d02204e07c5fd160d43309af90d278322e05aca85418988756c1df01080bd6631fcac814104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914b256082b934fe782adbacaafeadfca64c52a538488ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-uncompressed",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0732c0000008a4730440220082e908bca23a29f694611a761d41588274d87b97a9ff1178a99943c8d91605c02207b4bf2727cbb4dfc696023ccd81e203506f28014e5686e3f657f9e475825497a824104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0732d0000008a473044022024265d6a827d56b823062119bac5f8deaa6804a490e74f43915381ecdde4760502205aa092365b6fae7b4cb643f153262e5e0e8eb59d8befe1f3a4254cfa9ad1c5cc824104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914b256082b934fe782adbacaafeadfca64c52a538488ac",+ "amount": 2000000+ },+ {+ "kind": "p2pkh-uncompressed",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0732e0000008a47304402206bf3b70948eb92ab7e6347d058e53b0f494d303a46f27a36c4209e1c0efb472f02200f8ff0d48a995113c2b5094192ba1f97ba16373fd281a83e4deb20e560942ea7834104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0732f0000008a4730440220206c11b2bd7b19113ebef49d97ccb6652799b8708d0aa15c969c0b95aa10a4c70220182c4d96f66de7e81d424a07b777c23802d4b1637b51586663f6086d6c16557c834104466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276728176c3c6431f8eeda4538dc37c865e2784f3a9e77d044f33e407797e1278affffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "76a914b256082b934fe782adbacaafeadfca64c52a538488ac",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-1of2",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07330000000490047304402202c03c419ab80b472dac7817f22498256de37b3d54c5286995210a747cba2957a02204f069714e803a5511dfc975034ad4127b52a2b7ea24d88821152fa5460b3101a01ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07331000000490047304402205d843bcd16348ea42a35c631665a68f367133fd82a0256e19b919d2bfd52bc8f02207b0c0cc3372978c9a21c4476a3a6f3f4d3e7168ec986fb0e0564c769ec1a493e01ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2752ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-1of2",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07332000000490047304402204e74b34374248afd0b38838339fbc6be924cb11da065d5b125375c83c53a1d300220689f3fb8df7c3f2f88d9ee08491abbc9bc1598adbba7af8dc041244d331d487d02ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073330000004900473044022076eb582bdfdf42c8439fe39577cd9fd57582a28ee8ed7d9d531fef326726992902203cc5636d11cee6c33c031ff14be197cac63bd4ffe75bc088e1b301aba4e34d1902ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2752ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-1of2",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07334000000490047304402205926fa699ddf107361418aa6707823838b480014647042e021f26cf9366f442c02207069d2146f6a31fb0c0850033438e268f86ea6c8531e9a488467c2b614a949b703ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073350000004900473044022029794c5f68e8674448070e881dbc1c90d7c15c33a3eb4eb5bf7639ee683539f102205d9472d79c594916580168b521b72b09105c2fada21b7dc96ac11f3aeebd896603ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2752ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-1of2",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073360000004900473044022054c7baf3be180a52f98571fea2ddaff4cecbcc666b68460dcd033cbc0bf7e29b0220550b1d25924af027e6e6fcbc2141540e09f3c8ff9658698a923443242d95824881ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07337000000490047304402203bc685898f87e96d5b733c39b031bb75c132074893d49b4a2ee9d208ebf7baf70220624902c2d5104e2e0a16218bd65b8af155fe0c178cf69c5ac2b02e4841bf3f6781ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2752ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-1of2",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07338000000490047304402202ed05eac09b108f1a2f5877fc79057f7a26347f35842b4871135b407f3b76a0f022045a1c8d55ba76c8baad2da9ebaac23b133724afc432dd687387573997f0530ac82ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073390000004900473044022023b3be7011f5012c9606e0dce1f86bb940868c46fe6ba1a1b2cfe7a5540c61eb0220165b020367610d2c6eb20554ef74eb60147d94b0504acc405af669c04f28511982ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2752ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-1of2",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0733a00000049004730440220048a95cd96f6fe9180788436db71579ae02ea5eb48976aa4f96f1704508c56ae0220701d7d83b7e19317c52572c40344491ca29ab9d5e6d741631e1967c78d77656483ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0733b0000004900473044022071e9cfcf60a2ffaf0cf7c509b236d0458e58fc5c7632b166711a737f3fc89c7402203e1b88c8d2b69387c7323644a7fc9a39561406e8f20a4e3cfd158e479a67fbc683ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2752ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-2of3",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0733c000000910047304402202e44989158bf40737e8929fb597586962b3d131305aa3824651a0fae6aa21296022053f5dcd96a7be9c101dd9e92faf768099320e496b2cca5490df9b53f0bb6b3a70147304402203d58721f75ff60640c8d3aa2b9199a0ed067e3c8926f902252dd7295571fcd50022025059705c3d8d40d90fe5991d42e24219c4d5ffce2e4a567e0827acb585f9b1201ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0733d000000910047304402201f041853983609d71325362323a12281a3ab7b55fead7eb6c54486c6504c1f0802201487c6c582e84b464d36d57046e8e625951d8ac15e4847324fdcdab10ff05a8201473044022006686ceadbd510737a1537342f23b7f90e1e0841aa039abd58c1fa68410b581a02204966c04bd209d8090535d42ba2774682aedcb94c49f65bf13e7cc61f49ac46c201ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-2of3",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0733e00000091004730440220487e0c67fa3e49f816e16415dd31e1daba6e31ec7f53a6decafa841458ebba2202207069996ccc311e6479c6ba8954232f5a17899dd0b0640e7987f82129b09ab1320247304402203716e7d9ad20bf00cf59d652edcd22af51de4d4e39fdae4ebdb338ef065d64f802201a7fbeed44c97f4ecfc9c9888e0e6ce0a10094131b5b142d13ee7a7606dfe0a502ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0733f0000009100473044022063e3d08e81c2afbd15e25941f9eb731e3a87cf233385ec02f110dee023e3a4c202206293d17bec5a6de369168f5f2ca33ed79b93780183546648cc3f2d4739e05ae3024730440220109817eacbfe568d637a43da56979b19115bfb759f51938e15d7349414ece58902201c4b91bca9fe64d94da00ee6369055d3420e5aa0fd221a882f6be7d0d32a445802ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-2of3",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07340000000910047304402203495851910655386c1f62b49f1a84a62c06acb1af2560356007e0d9aadea9436022017db189c86a3f68a54c09f6c87f043b9f82d8b5a8d50eadabfc513f1ebc0d0ff0347304402201aa7df802ffdc7ac4ec5abda3b9d52a49f07d0ec3db7040b1e032586623c747b0220041c2a08f4d263cd60ad7e089c6eff00ae28d115c388cb240e91cba3b7282dea03ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07341000000910047304402206bd3bc23bd22e867c4245e4b75a80761804da9659913c241f5b808cb322840d902206f4d7f7bcf6be669821b501d1c3e1819b7dd19eaf41a0d861cdcd19107429860034730440220462e8c58594f968e124dae5c03414a0809af47a694c0d2205eea27361cb99d7a02200467ab0c04f92e8286becaf92471213819f75eea01b57de33778e345f87357cb03ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-2of3",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07342000000910047304402206920fbd11a1de530043faef0266e514dfac3d96c55a470760449387680fc230c02204f09ac93a9f8497cfe6bd36b358ee3908ec957e7a0e1b071b6870ad70e3c89a081473044022072fbfc9889122a22ce3479a63d99098771bcfa8f4d3cec301ba693ded4546f2c022048335517664d963d911ae89aea4244cf551793d1600e9822ac0d0adc874a319a81ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734300000091004730440220634d225af4abc019e4a908da7d15de44947d2217059eace8ec2faae01ad1473202202604a9d396231e4b0a60dae246c95684965b5da36850a59f0c644f874e4e3cf78147304402202bd3b6b4b2a37f2c5adc169ad31f864004cc1c6accd2474ba970c07bd6588d950220362422af26df79ae82a7ff95e66162254d2d4d49f18831197ed3220f26abab8281ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-2of3",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07344000000910047304402204e26091a3c7e5d05ca7ff2b1e285892d6896421a336fac3b86e9c4846ece8d0c02206832f2957b476b76c7d80ce468edf1873d5be4ca0c9421ad2e9ab91f8eced02882473044022002feecb839910d8c059c38228780fc663d3020d0da1daf7b46f5a30304e7627102202b340248c32a730391265141b768b11fecdddac594f4f0388621c5f8b31c71cf82ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073450000009100473044022012dd3cbb57e1728c2d9b75c71e19f9d198700613cc5b6d4aa5cf53b1f88c380c02206f275c7abc5249c81e738d9091e56f4029a7cea8ca8bec9c55c10429eb31544e824730440220760de12199f3079ac7db1fdbe763e433eac071cd3ce6a3efcdf4d5f412e15c750220686508470ce1f0b21fba1e12d9581dba2905721cb838cd14f16a2e1af9864fbc82ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae",+ "amount": 2000000+ },+ {+ "kind": "bare-multisig-2of3",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07346000000910047304402200b079fafeed8010ce7b7b6e2a165494c115e20d4a0111d0be13bef0a7f32e88702202d8db2f78d8da0cb6665b2df829a408cb69f0c0e12895c6288cae394ce498e65834730440220408ca70e59e30fabb80ff390f1640014dbfebd69a9275fcf402439db91587aae022066718bfc71d18837f8b0e5bca43a770249713f2752c81b94b88f78102a4fcedf83ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734700000091004730440220540d7c60e75fbf4f7c9dbb31864cd63ed07cb2f62188f14e19eda793c6d7c7d90220102f9d53f682264f57a2454b848c48b56e76be5ccc3d631cbb187ef6a9500fb08347304402203f0f2ff7f992307e6f1b80b0f3f9bb4ca6d37a42f92cca7498d466591a107e9c022039b17ee8cff16363cb2a7a0020f91dbdf188952e33b3f4594162d16153c4db8583ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "5221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae",+ "amount": 2000000+ },+ {+ "kind": "p2sh-multisig-2of3",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07348000000fc00473044022066b38151271450639be56f7911fa98a03b3d3e2716834743f017a37026645c7b0220238a801c5c961d685a6b0a466fd2194307351a0589a3fea138d976aa75c20b1001473044022056c5ec6fc10fd807659708fc7b8acab1024ab2ee595643480c5d1de4506820a5022053cac3596a7dd49d0244a3417fb9f8137d10bbf2e08a424ee60b78faf33f5feb014c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07349000000fc004730440220123415033d329b2dd5399fbef5a39b58ba575d2689f29440a96026009a94ec4c022044ddd5956121609dbe8e6697f1bee37406744c593c098fc666ae20f47e82883b01473044022014d5ca593b59c5aec786923027a1b4ce04b267d9a30e39de96b6de25091049d002204c50e60e3161c39600b3a9848697577dc6dd26723017339b123fece3f1590dd3014c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a91419130817a355e1a4df9cb1e25052d39374b83be887",+ "amount": 2000000+ },+ {+ "kind": "p2sh-multisig-2of3",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734a000000fc00473044022057c38a21d84469f14420753f018015a16acc247321cfca4b1bfe4750d831196202204ee098d0c82c521774fe22cbbabae4ef869bb4e8ac38f155aef837a0fb21989102473044022024749c4783f0b6572191156ef571235825b185e75b27fe83e804777172bab38702207b9513f48c08a3982bb7b3e30fdbdffb0de2717ccd64941b9f6f9229dd225a12024c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734b000000fc0047304402200f9bc9efc504b2657b30885f1d758fad09dd456f5a67ff02e8d009352f9ffae50220161a840b06e054ec9e082f95fe85c16696225e7d9064e5fcfc2ae8b287dec9d602473044022036960252f49f2bf54c1ed5382f31c6a7d3d2e1b6865269a3093d38024e66543002201cdd820f05e9cdad17c5b7b3e63bf4b1882e7dfa316ce00ad6c777552c090d14024c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a91419130817a355e1a4df9cb1e25052d39374b83be887",+ "amount": 2000000+ },+ {+ "kind": "p2sh-multisig-2of3",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734c000000fc0047304402202c498064e28bd15bd626052a17eae7eb7e637983779387e79520ea5bbbaa3f110220170f223fecbd6ed6d0e8de89a58123d3ae9edc1ef4d933c48c7e13d38e017d7c0347304402206c23f6b2b89ade45fd082f198ae3f90cf97c1e8cedf745e6316ea86a29d0ce60022014a758a843370f8064231c4adbeddd6d7713930337ecd1ff7c9d5f8d33dc8c9e034c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734d000000fc00473044022031edd50f5f2972d0511e46c88f881a4d2b9f61f04a7852680d1df900cdd7cd660220741d0a06f3aa88b5aeae160a48eb0f6b4f5f42a4eeec32fcecf9835528ffd61703473044022036a5c43221847e875e512323480bc0d52135931f4fc1efcb873f4c2c1b4ed14b02205fac1b81a0e0e65b00abd113d47b30958487600b83beeb20ea0202233259a655034c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a91419130817a355e1a4df9cb1e25052d39374b83be887",+ "amount": 2000000+ },+ {+ "kind": "p2sh-multisig-2of3",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734e000000fc0047304402201a9332aad5208bdddef4ffa9ee677f16f0c38bb2e3955db8537cab2b1bd95086022021be00e529d71398a1a8f5b81197caeece65125235cbf82380a8e9f4d7af61e08147304402202dac43b7b6130bbf493acc042467e5fed458e075f6d3f768884d63d53988cf680220395a3861225676e8867806ed248778f5c99ed7015228194ecb50e30f17239768814c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0734f000000fc004730440220304e026286ee83ff5a83f82a30160284dcb2b344dd65f4854daf387fe7fa9c9102202bb6a45d3939210c87ebf9b5cd7acfcf4524ebaba12868ba3607461a7ba9d7a78147304402200234abf2404455a5277e9d9872910ba7eb293b7537c6a8c6c6cfa3628c9a944802204396a7465e4ebaf1bb62b2549f92df094e820712f385c1870263edb9c6b7b289814c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a91419130817a355e1a4df9cb1e25052d39374b83be887",+ "amount": 2000000+ },+ {+ "kind": "p2sh-multisig-2of3",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07350000000fc0047304402207263189197bbc39e148890d430d595e101bf6fecdba3a9dbfd602b9acaf19680022055504831084922518c16321abe39bd4f908a6d9d8cf6b207b7cc4c77a207c7f78247304402207e0befaf61ad598f2b9bb1a1abdd8dec9ce848a73a91c0fa72213cc1f20809dd02200bc50c2662a45cc1153fc4d740235c75858d87d5f8f150b4339f37250d90ba63824c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07351000000fc00473044022020a84829703c92c43ba42c3f6037cc8ae78c40bd526c02e7b6d53f91a2e88f7a02203e9d6232dbc32f0ef448b25212e323115f7ca5628b145cd77fcb15967bc9214e8247304402207596176fc1b6a598cecb9133825301ea5228f4569469cb7a93260d18c1c3481a02201ec7a15c0cf4051767df6c7f28eb846d77f0942d904699b2f44c4a55a262746d824c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a91419130817a355e1a4df9cb1e25052d39374b83be887",+ "amount": 2000000+ },+ {+ "kind": "p2sh-multisig-2of3",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07352000000fc004730440220352cfd50305cfa395f55342413fffb5f44d6e9e5f1b329cf0cffd7de2e1d94eb022009e869b89d465ec1dc799c4252c70e1d58d1f3e9426e34290229c594cff7e9db8347304402204991f4bf9e2180fd934187c659de08fd29b76c8f4a126c3465196f6e37f48677022074e4733c0aa72cba43b133e9eb77fec20350c1fd383c5fa81c18c3faca863159834c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07353000000fc00473044022044e5e95a29fa23f0aff6fc61e47adb166805c7c4c3fcbba141e05c5c2dce4cee022033c0feda159b2c4505b0e46afc3dbc6cd9c1eabb68da744ac121a591aff08ece83473044022067bbc723b74392dbeb0df9ba76e4cb66d5c0286710056c62797198a65d8f47f602207e73cd116f635a457316da996a94057db9a873025bbf67ccd4219b7f4fdd8281834c695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153aeffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a91419130817a355e1a4df9cb1e25052d39374b83be887",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pk",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073540000006c47304402207b85f40a276a53a15235d8ee42b5fbe9ae1d5d97db2cb682d452e87c4556c99202200113505da7676365016eccc641b63b81b80f899db6c9cfe322bef2c753a54d31012321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073550000006c47304402200d9947789b70402445d357eab92f919dd420b093cee90d92ac371b9fa30ffd7502200d920497483ab4d1cdf20a58079e6f9803aa2537532b6793709edb03e0609ea1012321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a914defdc651f607ebd7fc7ae1aacde8e632f40fd64f87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pk",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073560000006c473044022042b741924ccaa76144797bfa16bc5b3ed3926ceee7109da78e4d5108efeffced02200a7a1bcdb06965058371446cd794d84dbc0c6203e992ac3d7ffa876687bd577f022321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073570000006c47304402205b52dcbce3a4c7e02c30c3e0e2bc6659a6750ec996777fac1555fe22c15b36450220330035a9f25aa159b4655f552ffa16eb9ac4bb9f6efb27e369ee1f6838e1c34e022321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a914defdc651f607ebd7fc7ae1aacde8e632f40fd64f87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pk",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073580000006c473044022075c2331485c58d4b266cc894fcbbabc8ed50fc59ba84a4d394c0ca73c4f2d3b602206a5ecdd34f7d60c3e9e4af1a1e68198160e2d27705e84ed7762565b5f7f31251032321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073590000006c47304402203435202621803d2faf7d24327a4e84824a836b85f4cc63d87b43bb7a029b081e0220410552e32c6cfeb9cca15b3653c158d427f4334bc2f8c3ef73bf247757031ad4032321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a914defdc651f607ebd7fc7ae1aacde8e632f40fd64f87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pk",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0735a0000006c47304402207b402d445c2f78c4db31cd62ea4bae5be84d976ac4e34dbc2b313b74879e9dcd022041129bce2f81c6aad30d21e8bf3b245703446da4a8a20617198ca30f7fff2eb2812321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0735b0000006c47304402205361ba5f55c70435cb40b4916afafc8925e4c6220c4e32350304be779f46049f022069032295a34e2feb0228f30048b89c774cacbdd6bfce334c0170e1841c7cc3cd812321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a914defdc651f607ebd7fc7ae1aacde8e632f40fd64f87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pk",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0735c0000006c4730440220658c5bd18f3eb5f44ce86654b759b9d8581abba4ce5711bfce1e1f9f65222921022075a72776a47326b737187205140d687e240692e4c79835939aee103d6ec6b6f9822321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0735d0000006c47304402201cbdb986f1d3e3467e1d6133c4664d9073048ecbd502ca87fcd2487228ebc18b02200cd71120944fe2866d56ba3d2372c63781baa613f7042138d171df642606ab1d822321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a914defdc651f607ebd7fc7ae1aacde8e632f40fd64f87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pk",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0735e0000006c47304402200d0e37eaaf781f240c779e45e3e853132816d28a1aaeccb31a65549a5f95f50002207a0323290ae02f099c667020cbdd842e2bb8130643e71fc189c4d966949418b1832321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0735f0000006c4730440220494bb27590faf5f4317e6423bbf8f2c6037869afacb438144652f908ca7a3001022012db90fa5c51ee8b10621951224f1cd1e35a8894eceb414f360031b26b7a1783832321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaacffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a914defdc651f607ebd7fc7ae1aacde8e632f40fd64f87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pkh",+ "sighash": 1,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07360000000844730440220622cc22edcf795580025b8a89b649855a9f07e38bd204b8f107de3c935afdd5402207c4754046994e66457bacd21c28c9d1102190a87457627d754c75c9ab1dfaba50121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07361000000844730440220257d299050856d1f4e110ecfab996556080c668d38e3dc227ee90cea635ac3bc022041d83298e5c9b996b35751eb861df6020b64d141ea5875ad610a603f47e055120121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a9143161c7bac54b3849b493ddd011b8549345df8baa87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pkh",+ "sighash": 2,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073620000008447304402202c643ebe5ac49d1f733781fc0c49fb30bc7d47ee599e9c48fd82890d6369e5ca02203c1c9b6d066b86f0b42ad192e61881f14082af8bdd443852e554bba572ea47950221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073630000008447304402207565d64e8a58a7c6db2b66c3d410d6cf510dd6ffe2e0686b54c666a4cc56b90d02200c3097fb4289c5acc191bb6f79fdd9b98c1d9dce3df7d1e4bf29a868fa431db60221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a9143161c7bac54b3849b493ddd011b8549345df8baa87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pkh",+ "sighash": 3,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073640000008447304402201cdccca5b1547f09ba819191fecaa54acbd32c3a013319a4219b7ad93c38488d02201897af812b8127b70a19a7c9b9cf68df5a83cf6a36af025d6cafd624a10d63040321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073650000008447304402201b76a1442f2db1ad539463232b2242c4312f1a34cca41b174eda65e19a093af40220486bb89dd25c9d50dcd1063e5d21db568f47313803a438c94524542dbf3693570321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a9143161c7bac54b3849b493ddd011b8549345df8baa87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pkh",+ "sighash": 129,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f07366000000844730440220204dc46de3b37d0cf96692347ca6fbcf64fb1949f95b6d9ffa1200d3ab5bf70a02205f9ea1246332b109154f5a6becc962fb839e8c7bf060f3500c21f2e5de50c4878121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736700000084473044022023e708fd5c26c4e6bb89e4498b2a5faf0e6bddf434724320529c8667a2468a12022012e67ad65a70b323c70b2a0e9a089168b13a3710e965d4513aa07415749670228121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a9143161c7bac54b3849b493ddd011b8549345df8baa87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pkh",+ "sighash": 130,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736800000084473044022009b38210cce493113440bae4375659a0c99457ade8899cb75ed65d8aebcda52e02202e9ab36ebd5a44cb11d710a87993bded95944f73c4d31e4af3b3194160b557818221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073690000008447304402205163e057c885de79237dabcaa3811275cb11a5a9f789f42878048f1e19f371eb02202a2e61540c197388f42145a85421441e1f3156270c1afbc1fcbb0c22bf1c2bd68221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a9143161c7bac54b3849b493ddd011b8549345df8baa87",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2pkh",+ "sighash": 131,+ "tx": "01000000024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736a000000844730440220501b1c4604bbd12267c45eb631dba9dcea1a010381e7558b2982fc0a8ad1bf81022007a18547ea2ed05b995affac9d3cab1e9d93ec3ba70a0bcec37cd472f20f49498321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736b000000844730440220141f6068e4173e592af3801855caf30e9488b877a71fba727cee425fa1a9103c02203558a22251a349292c51ad3f8aa34099b3340587c1639c332b8054aeb4b59c6a8321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88acffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef00000000",+ "script": "a9143161c7bac54b3849b493ddd011b8549345df8baa87",+ "amount": 2000000+ },+ {+ "kind": "p2wpkh-compressed",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736c00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736d00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0247304402205c54ea213b56a4707b9a2225c2ddaa3448c1a0fc030eabde4c38ca2b4e3c59f3022004ac7d674841b773ce95b1ee030b8ad9518f8b16508d105aadaedf7ea010c6a70121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402203d5f106ead625a15fb9af2a434bd11a9629edb0349dc5ed90136deedd008e73202207a7320e73d2b37419b6bd6fdffc16fb175de4891b2e4d0eef4bf0751df5583660121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "0014fc7250a211deddc70ee5a2738de5f07817351cef",+ "amount": 2000000+ },+ {+ "kind": "p2wpkh-compressed",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736e00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0736f00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef024730440220683fc430f76da0cee2dcb284dd2c384ad7fdd29cdcbc801d791b005bee29d2e50220684b48114274d3a1b4398724c8b9a97b8aa8ecb0d1952fa9bc884c9393fcc2d40221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa024730440220782c8f2afcef060c14f93d1bc4dd80aededa8c2afcbdac1ca9d014031e17aa84022051eb2b739625a3971fe80cde963d269eed6f20d14651ca0acee715fee827bea40221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "0014fc7250a211deddc70ee5a2738de5f07817351cef",+ "amount": 2000000+ },+ {+ "kind": "p2wpkh-compressed",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737000000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737100000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022020b4c6b63d30e5b6c2ddd9d290ecfccd424d5191626f9c5d5c282f99a21f74930220387c662e5a80fb96ad05d376dd0e392c861e69dc407b7784872c77380a11818d0321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402203a6429ac08009781aae8f693f8564c01d036bbcf23c0a8e3225c43b911c2d1e0022036bea8fb248772f20d11e4f58334d7b1f9b18582cade6272ac294c2ea3e61dd70321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "0014fc7250a211deddc70ee5a2738de5f07817351cef",+ "amount": 2000000+ },+ {+ "kind": "p2wpkh-compressed",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737200000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737300000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022036f82d724148b2d11feddd3f785cbe49b00771d05a246703d9b753046735405202203bd41261516d8aca66ba4a77c3bcfe09b4eae2cd3a7a0d8f86b7339e2cb83b818121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa02473044022039c2e3747ae371a08fbdb4a8c559892d5be197b7dc47e0950f03cceba5c5422502200bee786403414e091a6a3563f879dc0b6a72308b7ac3ec266c0372d8fee9f8f18121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "0014fc7250a211deddc70ee5a2738de5f07817351cef",+ "amount": 2000000+ },+ {+ "kind": "p2wpkh-compressed",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737400000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737500000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0247304402207c16b45bd31c671da524e4e7a3fec84b179edf5c80bb9ab823ae30667873de3a02207ff9cc488e8b3df8ea82d8ff6162cc97244f795bbb31c7cc2ee93ccc24f59bae8221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa024730440220100b75d07f013b7d903b664951e6b258a0756dbbaf210ae1924b68411ea94bfd02201acfbd70b3b662954d83eb13459070406843924f04351ef8289a589698b74eb98221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "0014fc7250a211deddc70ee5a2738de5f07817351cef",+ "amount": 2000000+ },+ {+ "kind": "p2wpkh-compressed",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737600000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0737700000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0247304402204e6a6add91afa327c35b44d8818823754e5cb9cb4df8e15370fa8b75d8fc04ec02202515ef99eec0ebbd9a80ab0adf17ba2bee80d541827eff6b94d42aa2625fdf778321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402203d2ec1aa45c903c2731c08055ee6a91e4bbbe258dacf82969d61e640f5ddbcb702203db8893e74e56c7f4e83fcb7d09e2d71903c5818026a1812a94762ca062ee78c8321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "0014fc7250a211deddc70ee5a2738de5f07817351cef",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-multisig-2of3",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738400000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738500000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0400473044022051892dc6dc5e482369252b5cd13e243ad9ff82e97ed75898fe5d797d8624fadd022014d018f56c34cc56532de9b162f911a6d22c5d0adcbc68fa48cff86fc9e5bc7b0147304402204bbe2594ddd7423d68b5090eef60e5a77b8aa1d1351424907f14e84f290b462a02207b625b8a0332433734ee367c907c36e37e9e16d14081baf1dd51946bcb347ea101695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402203f44d79e67897444f697975cf1974100dac6648c333bc986109c242a3fb9091d02207fd7fefe4cef5daf3307180eadb5d4500af95d2247525f1609f8601b63add35e01473044022028e7a4c85ff1ad6838df65d897ca1d8a2c2146c00bbc929988fb085a3f04b93102204535ada01b7bb9c3da14f3d17a820a65d0d4a1ae8a4a742bc937bc334d78c0c701695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "0020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-multisig-2of3",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738600000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738700000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402202d6c547aa1a6fcb7b5a7ba0faa16af8e3468e3eeff0ddae2b924c6a951e9c0bc02205acb1e256a0f5f418c2d33373c8b7ace9286f4faca9aa2a13df6436c2f89056802473044022038c4f8a581e751b2016cbb029c54d58983346c339b8d0fb63288b31b2e77a47302206e4121c528145b0fde57cfe9304c7b74e1e758dcdc2da40f4de728f04aa771bf02695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae0400473044022037d7c66fbeb2d31c224293d335f2cb8d6f74077934575bb95f41f97706d8f25f0220070a29e5f4658b199efc685aafd14ee7d958983bebe67ce25d3d777e1d22de4d024730440220416137b02f447d5507d1aa76da0003f31e290f363a8220636e997aded18e1e290220424267f6fb1ada3f5f6531f61d0f2d4e38fa7e5e16901239017d1dfb778d3a3102695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "0020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-multisig-2of3",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738800000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738900000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0400473044022012a2b4d0ebec4e2bf79d44a82a401dccaf0e03c1fd916d698ed731b74e284edb022073aa793c677b63bf6fa0f0870955f4c824fd2088c65b5584def77f5447988938034730440220170aab2379b64a15b538be44586525914b6859eb25e8ac7399fde6f1754787d40220207a948593b8d65a2560c9de509e08e21edd1abda17b8aa5eee756f2f3dad56603695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae0400473044022041ab42c24b38689bc2e706e67ac5c97f2463f2266a7c663459e92b202280d86402202a5aa808e019aae087b89e42d293b13096065f0d2a51ce92c0208ad6c5ce4ccc0347304402204b11beecab5b3901290cc73086ec086b4239ae306c96e2482251237d90efc791022023ca949d2a5eef358227e3895c8875844f3b155a74da52d0bcdd6f4f34114d7a03695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "0020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-multisig-2of3",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738a00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738b00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef04004730440220359ae160f02b582c1245899988073e0cf46000175c0a06ef14e303a2713a60c2022065c4d0281ce60179962b5b10399777de9f474681171af575231fd06b4ee67f0a8147304402202a323a606a8b159e5615c259f8fa61ed291f3ff4b9736536a574fb8687fd0ff80220599e9849302d94312378afa00ab06b0acf39876daaad05d806edac3f016b56f681695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae0400473044022005fcb340fa8d34e15df8bc30e564ebfa086fe8844073a66ab72fb1bfcee06871022055c8c35b724295814a5fa336f85e1d626d49719deee755da566c5535554becd18147304402200f3e3c739f3c82e78af8b0a4239e03da76d71290cd802f3cd3fe67f7b0b5c08f022051d3e1332b6ed54493ef6e596e5b0c1acff4adb45d3ec52eef42b29d13b70ea681695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "0020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-multisig-2of3",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738c00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738d00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402201388c1d6b9cd6a6f8ad90fc419bf1d19e29edbf3bf35232563caf80595a1725d022064d6727f74c3e0aa7acda74d13067a90ffcaafde587563202059db0792852ebe82473044022039c44ea437a895d6db25a877b6594e8f33e561c6540f37962440474a377d6a7d02201cf6af2401730fe0cdc1a0695107b587806a8a9ab88f011861080b65e300a81e82695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402200dcd03fe97e6cbbcc557d453a7e168218877913de98fbd4dd651f080981d9d5d022009870ef68e07cd30604a413c8e26649a0d81b9fd5592a7fdefde03f1cdcc149182473044022021678836dcf9d4a9424b24eb8dd509d91a9f9e014f3ae05d089aa3603c83b35f0220270082343946f98ed89996456fcdcc8e521a87bc5bc22eeb266c2e55b0c3149582695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "0020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-multisig-2of3",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738e00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0738f00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402205da11d6458e2eb985c218fc4f84af900fc64761542fd0f5a67284273dbd7c86702201c94eae730e6520ef13d53ea88d6e0757cbd2d8693ac092ac84841e9033276a7834730440220536645be24ed53a2e09d26758f0142acd42cb8ec319a67269250e8bbf9aa3d4f02203c4c5e12d0e5b84d542a2cc7f5ec352f121d8100e1d801879c9e6cea18a8866a83695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402207f0af0a36fb4576ec42f5a2fb195aaafddfbdc50239d8b3a93ab9a12a260340b02203eca3fd9ec0adaedf87d296db70326302ecc432297f5e2a4baf398a39fcf10b78347304402206f7ab7cf009f0c4547eafff8fe0d23d2bc0916dcfa1764d525609f9710e4f28702204b974d7d2700783e1780bd12a2bf8f9dded370bb89fef6a8545ead1e758cb14883695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "0020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pk",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739000000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739100000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022047e219693de01d455e78aea8bc257dbd42e16a0de381dc647975a2b696381ba0022007ef511dce6748e20df3c52b8a5847e733647bb8675705937db04f91e5259972012321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac0247304402204f571135da0d235a12ba6ddfad49c03aea3f7e06f310ba3ba6c41c59bfd3ca3d022041a6f9eea5f5ea4d1c3429439d34f31a7248989ed6278ef9d7945532e3f04004012321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac00000000",+ "script": "00208f30ec1925eaa1916f4d67a9742d938c247d220c796bef6550111eaa90327245",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pk",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739200000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739300000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022022d11e579c730324434dc8c38ee7bfa488a7c7ebdccfb7a91e050ac746d948110220598c34a2c684ef1c620059d9c1ec9bb89ddde1847a9c384e1f9c52001fabf3e6022321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac024730440220344e7439cf177e8c775bfd99f79eb2b8125b50f3e306b1d2fec7c8557138193a02207e05a604b1a71959f2f445f6ca094a4b36cadb21f4f641eb76d4f5d57802c432022321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac00000000",+ "script": "00208f30ec1925eaa1916f4d67a9742d938c247d220c796bef6550111eaa90327245",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pk",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739400000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739500000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022017c752818f24634a46ea847b145314b7f0dda1a63f41cb8e54965f670669461c0220116382753c585d1b33b6c541a4186fcd221b7a72ae59e76874df2ab82c61d8aa032321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac0247304402201844fa020fc5de01e3d091ac438fb726830986137598981780e277467889f3530220102643659e131edced6a4225d493d5386704ebb705d9c52e7778f0c8e052e945032321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac00000000",+ "script": "00208f30ec1925eaa1916f4d67a9742d938c247d220c796bef6550111eaa90327245",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pk",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739600000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739700000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022057ba3f26e7e1cdda5abe1361ba423beb74c4e6d93f5a85027f852f83384b0faf022023bad2b046d9d5e104e37a73614fbf61588e70eb7fe5d53521f48de13781fdb7812321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac0247304402206c6ec58ec34ff37abfa50d245726677551cfdd2d1d4bbd479bca07b2f9de6e1802206e65f433349f6db634507fd8ce3ba3ef128694656941613ddcc0a0661b5c9bc6812321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac00000000",+ "script": "00208f30ec1925eaa1916f4d67a9742d938c247d220c796bef6550111eaa90327245",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pk",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739800000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739900000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022039d87d464d4c306b049024d8c3eaeee27982180fc3c9f17e23d453cda4d45e2f022023637bcabb7059c5b9f3ce78c7b4eec6bc5eaf06d112b0e845f727482556e9e7822321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac024730440220345e2d317aff8f81813b0c87642133640ec3621f844bdf572a4a0eb6fb737efc0220417c2dc97ca3a92e6ffdec0d143233a73e59ff41a5c49f0a4cb72c461b395a43822321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac00000000",+ "script": "00208f30ec1925eaa1916f4d67a9742d938c247d220c796bef6550111eaa90327245",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pk",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739a00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739b00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022071963909861d4876084f8433097a7d7c92e3f363ecba827b4630a5566d2f29f602201cc21d1ad4dd9ef46f36b9c7bde567f2901374ff6dbfdbe7f017e718077d88ba832321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac02473044022025439dc7a4cd403e8a10df406c22ee657997b48a728c62621707db8819c9a45f02206968133d20a2fb04846bd540db82d36bc04cb1aeaf88ef2b9a19b2b74460b15b832321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac00000000",+ "script": "00208f30ec1925eaa1916f4d67a9742d938c247d220c796bef6550111eaa90327245",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pkh",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739c00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739d00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0347304402205255daaaf1a414eef471de9ec8c139dfd030ec96548571529139cb5eac1c1c0702200080125fd7ffdee360cbf7b7d639c6622ad1f4400ec6ba01b52c3bcc534aadbc0121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac0347304402201d062523a6ec81aa2d49e0fd2fe36e8da08852bab64df3ea9c6f39203e9e0f3702200c72137acc29be5661ee269e15ebbb44845a16b44cb9806322dd939e226ad69c0121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac00000000",+ "script": "00200085f44548c4e5276a92d8321cb79cbf7fca6333b4d343cd25c853fd855b7c4c",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pkh",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739e00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f0739f00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0347304402201eac57f1b6b2f9cdcf94de615eb43c17ebc3e92d1d7f5c5838286cdeaccf29e00220550035e81dc57a3fa672ab0d085f4d670577c59305a11d66f17375fc1640586b0221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac03473044022026ea930230c1332e7e066cc97e52967b512c4585e7c69757c536c421157e839c02202ebd9a00a54776a66aa39d048a6403ae6d2fa9ed0f89bf28dc9efc73301395230221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac00000000",+ "script": "00200085f44548c4e5276a92d8321cb79cbf7fca6333b4d343cd25c853fd855b7c4c",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pkh",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a000000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a100000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0347304402202f5bd145a41cd69621a6764eb36778b2be8f011a9122e400156c7f99cd6cdfe402200d51df2c553851afab9311edc9fc1a43ee9879ef755b06b6afdecc855fa9eca60321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac0346304302200af5f498237bad8524676397a253f26d1ae6170a4ce4bc329418b110837d5a36021f60b151427fa2ccebeb8f776a7f8f23a560e1210d0f45fba4270005da8edd840321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac00000000",+ "script": "00200085f44548c4e5276a92d8321cb79cbf7fca6333b4d343cd25c853fd855b7c4c",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pkh",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a200000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a300000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0347304402207dd2470f0413747914615d6e0a2cd3fc99480710ed2080070a52f177f17cc89f02201136dcd8fa8769d37d03a15a5830e85a8e7713658ba40402b6e7cc9038954cbd8121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac03473044022041fd5d478ecc912193455546d101ac3b9be79d4987b00a30c6564d8df9eaf4a4022001fbc3f6057f9c5c2a7183acb807702c0561b926421429d803062febda9f6ef78121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac00000000",+ "script": "00200085f44548c4e5276a92d8321cb79cbf7fca6333b4d343cd25c853fd855b7c4c",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pkh",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a400000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a500000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef03473044022019801bf5dc7be46be0370c5c4399ca60cf2941e44d31b8c035d60e021973b329022026b5445cad1a43af7465d841d4d2e8d22e5ca2e88c1e11202c6ba508ab9b99e88221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac03473044022074150dcaf5cc5482fb299d60cf109ab162134991677b2c07c18c568ab6c1445b02207cbe5335508d10dbd3ae549ba09ecc5d9af0db282d5e994574d64b3b4de445e68221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac00000000",+ "script": "00200085f44548c4e5276a92d8321cb79cbf7fca6333b4d343cd25c853fd855b7c4c",+ "amount": 2000000+ },+ {+ "kind": "p2wsh-p2pkh",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a600000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073a700000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef034730440220093ddf93976553b78c7be6fea44f3da8d8193f2518eb74d759801b006c4061b40220111e13e7719727b02ed839bfddc0a6a8e5c769593c622abba86dee363ec2206c8321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac0347304402201b2f68d12b2ae873925264b3a0d360435db505415a58c948f9283ae15891550602207b66a98459625953cc09f0fb2a81ff1c99e8c77ceb17b1300b326a466008565b8321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa1976a914fc7250a211deddc70ee5a2738de5f07817351cef88ac00000000",+ "script": "00200085f44548c4e5276a92d8321cb79cbf7fca6333b4d343cd25c853fd855b7c4c",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wpkh",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073b400000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073b500000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0247304402206afbbae9dbaf7e82230798b9426b025803dc5433d44f67e0cc977ab0d222a6f2022009fe2032d2adf3296e0635c4d0adf33ca8cf65523c600ad71b19686d7cae26070121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402202ec4974bd7c61fe917ab52a5c6b8cb0ff4be07d616c2f48b2ebcda84070cd8cc02205792f343f45598321711a5f344233d4db383bb916cc7b72bb8d8657ea5ff27400121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "a914ec8f3d9c2763a0997a465b968d99db47e82e69d287",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wpkh",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073b600000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073b700000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef024730440220472431020987c2748c2c8c68dfcd634fdd2c9df7dcf187da9df01dd5e5bc2ae5022028226f5073e85fa838c71d6a9fca6ebb34ff0bc375ab976eb5d7696f25242a910221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402207cde8bc59f125bd1a8fec7d291ae88e07d38ab698c98d1571cbf334860cd2d12022075d66e59b5a77f9f3087d94af1f9538202947d4f698cca242dabf3b34da427d00221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "a914ec8f3d9c2763a0997a465b968d99db47e82e69d287",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wpkh",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073b800000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073b900000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022058cfbb968f33e4c74cee22175a1d5d84d0e18a44fb22f663cc99b7c07be275a602205f4366274756917a6422b9681d9798c9a12600a02013b238086fe94c19cada9b0321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402200338d28bfd3cbe9730969e7ed449023ffba55edf47eeee935197350b1fc75dce02200b12e93a061fb0f45d776f5edf9d77fbc32d757a9aa36e3c4f562f9cd28af12a0321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "a914ec8f3d9c2763a0997a465b968d99db47e82e69d287",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wpkh",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073ba00000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073bb00000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0247304402202fcd2011ec7e30f6e3da9b4581548436f673172e6233c9d1e2cf88a755e56e08022008b9065898fcbfa10c9a28467a82be1b6166ab0060f73b23e7c78687271629e88121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa024730440220751d4cef6c53395ee09be9c0185db31ede3996ba44dcd512287b1c6fc4df0cc402206d0a111c74cc017df4b77d9be3bc928cef834044330337446ec09ee53550b4688121034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "a914ec8f3d9c2763a0997a465b968d99db47e82e69d287",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wpkh",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073bc00000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073bd00000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef02473044022051a98a1a3f22f608fe446524184f1da3f297ff374fec9244c76df0b86182982602203f23d47addefc23613a0b0fd0b5cc3e18adf17a82f5b8914785d8b96d039a4d38221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa0247304402202c65ec18248728c62e15fbffd78d3a5f07fbd17bb8dbda819f88076914995f3c022045bcabf20c081c986863d875ac32e4f94a628da50a06a440f40de14126575db08221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "a914ec8f3d9c2763a0997a465b968d99db47e82e69d287",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wpkh",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073be00000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073bf00000017160014fc7250a211deddc70ee5a2738de5f07817351cefffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef024730440220152e00c6ae2015f0ef445105b9cbf742ffa286b3112687714215f9190de3238d022023ff5e4f1759ee32f722e0cc71d5d586b760b1233c937f08b5cfcf4609f0a7938321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa024730440220724d1a180ff37826ac634bb1cc6aaaadb5fed9c3aae64723ee64edfd24949e5402202a8668b79e6a79b172bff8bc0c9ed3e7f0a27eb10eca217f91e6a5808b9bd4e18321034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa00000000",+ "script": "a914ec8f3d9c2763a0997a465b968d99db47e82e69d287",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wsh-multisig-2of3",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c000000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c100000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0400473044022032ff45b15adc9576f39a78e8cfb61fee598d14b065eb87b3a48fef5b1e06d85c02207acd8dad5b524f7c25de8396fa3c8b7bf6181a015f98d3c39d8570fa65e510d7014730440220777b1f7ae1ebbe01da66537b77ba188a2643ba0de31970d40546ba59907e31ce02207bdd1338e952a0410996f440d997562ce9fb1e4ba2b72939cbeaa6690e088d6201695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402207c50ae837e9afd62ff75a862bdd005b1cd98d98fd7e2e5eec3d8c0da1dadd25b022044fbb0f19a600849b31081f016fb876e4d12268cedbcb897458485a687b2de71014730440220625353a48038147083722deb9130a762b18ec27df5b2a5b98affe96ea7031096022011de99eeeac9ead2a25001ddba8590b928908f6041ec8979cba516edf28ca7b101695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "a9149874cbf3ba7eec9fd18eb1e9c4c9eded95c791c787",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wsh-multisig-2of3",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c200000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c300000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402204009c630080bea5fc1b06d1a37037fe889f84619de9f1e2b3b9a8f81c1ca7f0402203314224cb3961fd4d49359a11e92d9b25d8ac3b5ce6942e459bbb45edd5758b8024730440220194a88d833cafbf0eee4407cba0c7254c694adc36f3d51df5444712e02363d780220008972a2381f486f6ecd125547552253f54b60becd034e0a0b8d0ac302462be402695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402202954057d893fefba65b32406f69a523600e41f5275f1856be8332b4ee3a71f9802201af7ca064834fe33f051418318cb7388681ef48002b235dc0274c3829bd63b800247304402205a53760c6d67d0e471cfd2672119f413bda7cdd0a329e14e90d2a68f5054c02e022019a4e5df356514f429a7132339c3772bc5413b6a860c13d37e923a3f802d84e302695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "a9149874cbf3ba7eec9fd18eb1e9c4c9eded95c791c787",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wsh-multisig-2of3",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c400000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c500000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402203bcd9ae7d2b0bd4a4becc165aee9f3c24232dc2167109fe7116b84ea2a1932a10220627482c372eb0745ced44c3b5f63acc119eda9d92be5a7db0db29e5f225a61e90347304402203eb4f8aa5ed3bed6f64d727db7bc47c04fb25b595171e548173a44992db93d3602203fb86d93e958bc4ce8cf4d3cea9e81213d5ef15c4b593cb4f3bd3440cef5676d03695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402204a149e7690a237be9339af08f6e0db58dd1028f4b485875f68e4822adeee4c6b02202cf6746459ffed866cd81231abee47cff049e5ee4b59e8ce67f86523583802cc0347304402201a3fd47cf5a86c5292af4750299db6c44f55252970b4afde78fa02c3b5419c9502200b4bd526f18a03f5a79c11880b77dde959ac5bec02fc74688ba51132322d350303695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "a9149874cbf3ba7eec9fd18eb1e9c4c9eded95c791c787",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wsh-multisig-2of3",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c600000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c700000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0400463043021f49670f225b8bb7e023cbcda912ff20266eedf270aecd91269f3ea1d6c638ba022043130654c2eaf3a940b858d99a2e57f2df59df5c8f7e2d470f548e75209683dd8147304402207bffd35a522306906adb2dea0e04a79e392428dda9792f31270dc5049c9f7566022032cab77ce9701ce77e2b903790450572aa2101ad6b4df49ab3ed596560656ff781695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402200e97a174abdfa0ac529848d3e9a9ed6b5abd66f446dd05c487dccbe5e2ac9813022014e18955eeea59e6e28ace238de77bbe4508fbb851647ddb7c93ffa091a7b64f81473044022031f38ae252d6406cd58129b195aa4ca48e4030f049401df7ec4b185b12b672f402206a691e594bb57a09a14c25466e38fe8e675fb748de424a59118d965f9f4b64ab81695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "a9149874cbf3ba7eec9fd18eb1e9c4c9eded95c791c787",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wsh-multisig-2of3",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c800000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073c900000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402200149f907269cff5737653e98dfe17e79351bb9ce9481aa09788f83dcfcc0cd4f0220264dec8321376216726318a2451138fae7a4ab7b0fb25032ad3313393e89ae3b8247304402202da268625e80ee074db140073ddf535f89fdf8903ade16280fe0429befeb1f8b0220183ea0f784aae05531f57254daf817e81077b713d05f7246a6797abd6eea7b1582695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae040047304402206134409a26f51ef97bc6f9eb750407cbdb39e337e1d6d9c9695a9ddcbdb2c9c902202ab14fcb42f6a42461461c79711168cee927602193194bfb2ff92c3434b872d982463043021f69864b3a75642da059c979ebb2fbc71fe6dce30f563feadf09137f5f04a08b022024f75bab325b99cb58619289cea462f2a68be03b972f8d391625b0572de461d282695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "a9149874cbf3ba7eec9fd18eb1e9c4c9eded95c791c787",+ "amount": 2000000+ },+ {+ "kind": "p2sh-p2wsh-multisig-2of3",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073ca00000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073cb00000023220020090e494a0afd279cb8de52b66664aed6fde1021cf0fede59088653fbb87dfe46ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef040047304402206ab27c2770c07d05ae94972c628f8f3d4fcfda219a96fddfb86a55a01c8138d0022010e794e724beff43a277dd28b7cc92ce3f28a3317055d27ccdaa1179fdb96fc68347304402205401c024e5e3965b81a4aa09e38acef6c57db4998f65d70a32ec904febd231c2022011998ff4ef4e974d17e2c6e88d209f6f768b5ea9d6e608507dc2433208eb8b5d83695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae0400473044022042b31a943ce8b5116c7a424af264c19b48f3f3591cc215f6667ec4d1a7f6890f0220196479633d6f68b9b96c9d6a7375f4d4efe5424777791b8f82acbe6c4063e83a8347304402207910b001e0b547988cd64f1e3abfb057fbc0107e5f7183b7a724b430e5283fcf02206178bba6c47f6782ee8ec22be1f91ed26632f236b2ade5129bbbfcd3aa944faf83695221034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa2102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f2721023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b153ae00000000",+ "script": "a9149874cbf3ba7eec9fd18eb1e9c4c9eded95c791c787",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 0,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073cc00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073cd00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0140a7525b59673ba07220287e317537bcb64610bd2b3719923bfb7211455c13282c4004dbb0048f847ce1dec6ff62ba92923e74bbc7e799ac6806a54454087492c40140ca61980350029e3a61db8d3ffca07ae007e42964d4b53f2b113106456eb311bd1597e1dc412a726c84cbc2b10b55e242f74f6a8c2ddf5dcee646b832c614b36000000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 1,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073ce00000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073cf00000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef014181d98d0bc23dbf0b0a7ebd10af0f77d290d68fa35d2b3848b76ad1dde4f88d0f083b3054f5d3468709f42d4fa0937bdbb5807a329c39a1704c2d76fc3d8705f8010141420e7eab87d312d23b79eeffef42680418e6266fabfd9fd3157be010b9670e94ea2859c0e9760a419b3b6f3773d54bc673259324c6bd9817a31273ffd98682d20100000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 2,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d000000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d100000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0141de96009ab188e2692a986d635b5de18b7f5f79623c0c666793f8850e081d40c84fe68ac1c95a375c2ce69c09b32dd2970fa5bebff6c04322a82f765db88f5d3c020141a7098a9b0dd0b6861d007cbfe59e2b9210eb3602a087d95d64ef460d0a273db4a213ca46ee6a39a277b1928dd2922f46cdf4d9e925222b52e0dfb5ad88a4bf740200000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 3,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d200000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d300000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0141fd0309c4b193aca92b6d1dae141a3829f52b680b8cec0b9d0a047def3239124beeff37dfb5325213188833b881c54e4d75695a4a6913c9eea720affb24b4882d030141d3c3f3ff95a127b64de9fa60c450c3ab259fa30cd9dab9b65c71c5840113966e817b1b7733c9691e4b92bbcec80210516637064aa57e38b00f6436c2e41a02710300000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 129,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d400000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d500000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef01410860e17bb9c33f2ed6ecdccee6d3b2b30422cda7032f73c5f5a4dd238b2fadaaeae9fe675127bedfe78ed711443cafdc874d0dc736a7e9f7d9ffd331e229e69f810141988ed3a0fbfa22d3156c9b4a16e2e6aecc73cbf581b4924b9543f8e5bd4aea054b127a784a13faf4a82126f3684daadb35c76f542de1dc443eb9d74a0846062d8100000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 130,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d600000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d700000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0141e39f7e087ba1d9d35900bb7706838ed9822b01471686a619d1ad7402a51b86d8337a745aa8b7f81d69888f3ac7e0ade6251f72ae70c9a2739459b8efcce301648201417128998c6a11aaeaba28b88b31604321bcf0f547349af9040530cd744b07c6b1b19c2080fd9f21ae99aa64d54c77dcdaf46a99685ac56491695a0c985686e9328200000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ },+ {+ "kind": "p2tr-keypath",+ "sighash": 131,+ "tx": "010000000001024d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d800000000ffffffff4d6de58f388b8ffa90cb8e97d7a62fd52d26b677b35ff13218e9fa0d06f0f073d900000000ffffffff02705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef705d1e0000000000160014fc7250a211deddc70ee5a2738de5f07817351cef0141940324907a0bae87ffcdd31f23c94a9bb0639e0694e33ebb42574057c4b35812604a96db93f9f83f79198464a142ef34cadf350cb32ba8f9544987df40ddc0e18301411a5ae3e6f80bc11c76aaf6a88f5313a8445b43824761a5655f45a5e49f24f54f9843464b72eb18051d0906411e0de5ad468a0a0fee468db91af4837698a127af8300000000",+ "script": "51202a64b1ee3375f3bb4b367b8cb8384a47f73cf231717f827c6c6fbbf5aecf0c36",+ "amount": 2000000+ }+ ]+}
data/key_io_invalid.json view
@@ -6,177 +6,207 @@ "x" ], [- "37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y"+ "1GAdfviErV2Ew95FPtZyikz2qGP3gyCB6Hyu94sedAkPpA523m3fQwps9YKUZkKgQckGPKhRsFR" ], [- "dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv"+ "37G2kMDLpmWVhimxRdzwNfE8JFvWXnJYnVcXeeGrek2qumdJuK7XArcVVpRtLLjRra3t64BEPF2" ], [- "MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S"+ "giymtio7u7oqWtmC9YnvAEKkLF3JQpAdkEFkVJKYrVDfaLbhaDpX1ihfF2vZmya1i61fwLPC3YQ" ], [- "rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf"+ "8iVk9nLM3nYwRuwypjy9NK5rsuZH7BbrQRZ1pgcQmvMnjAgRXD" ], [- "4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq"+ "cPTVQ1hbo4qdoysf6Jx5GthqucNmdfqt6J2pZRFeXv8Ep7Kmjqud" ], [- "7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb"+ "cQbR2Ny85XFBzUMx3Ed6HsTLw2pVruSgPvt5AofnBUnhiv86gYeW" ], [- "17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs"+ "2UB3iG3VJbX2TRrMwm6ssWskgvU9VjFBYSqCzwqkrihCwo7mg4mtS4WuGZgxTKuxf5A3EcotYEymz" ], [- "KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3"+ "cQe12pqwPR6ExtZKfrKf1q4b3CTh1Qi7MwuvMvzs79nWXDvESfBJ" ], [- "7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th"+ "tc1qeul5g2xfkvdkrhcfmdursv73ad64jnkjl9c40f" ], [- "cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va"+ "bt1pq65rzej5glw3ra79gav6fqnx4haa0z257qr3mc8cggkefahmgvyseufhc0" ], [- "gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk"+ "tb13hty4qmumlwpp6chxjvcyzza4duqgtmxw3xhm3u9ahj4nyhtwz8eq7ynrj4" ], [- "emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs"+ "bcrt1r2qxpwuge" ], [- "7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo"+ "bc10uexgzna2dpfk0vjt35srz6a27ps6m0l89jweznt83n2sqn2fx4hvn9ym5af8wut34sfrqhk3" ], [- "1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso"+ "tb1qum6uh0pt4q253qaf520929737v63w5gf" ], [- "31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq"+ "bcrt1q888ryfgxpvl0k7vum8zpyar2u2sexvdhkf38ue37yknmqq0ycrwpl3w48y" ], [- "DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN"+ "bc1qdsuzmn04k2z8vryw8l4dj8m5ygqgnne5n" ], [- "2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i"+ "tb1qlj8es50nc8j8r8xshrjgzmw5azx89efghmw8ju6zcqla0g6xcnrstsjz7k" ], [- "7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos"+ "bcrt1qzwmyj0z924g7fzs5yvnrkc43y76RVyr2lh5t4r" ], [- "1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu"+ "bc1qpu6d26mrulzetu4jqhd7rsunv9aqru26f5c4j8" ], [- "2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb"+ "tb1qun6d26ufh77ghny6u5u8cwz9da7qwc6k4wkuceae9tth06eqlw0syupl4w" ], [- "8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ"+ "bcrt1qj7g2jps453kj9htk9cxyyc2nxe69x4kzzmth7v" ], [- "163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ"+ "bc1p702xksx4z3uqf0u2phllxkfe5cgu0adxptqs0uelx0tqt8e885sqryes2l" ], [- "2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu"+ "tb1z7gmh0v6pc30z4xum76lmw8w86yswrlmw" ], [- "461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU"+ "bcrt1sjsrw6nun4h502cr97xmnyyuhkr22q0s6efrgtu" ], [- "2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs"+ "2UVPFpGYnLHJezFzjUo42our6PMEoozzRdM" ], [- "cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn"+ "2MygHQjE1U33q3LSC53p69YqFjP8PihumJAF" ], [- "gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj"+ "KzNbAQ4mexfAxa6RKBzHQqfoTycaeWpv2p" ], [- "nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny"+ "2jDPrDfAKihCGPbPD9ztY8TswAia4V8Bc6vx" ], [- "L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc"+ "4VQUNG1hG64QFtaNyQZQWDdwpxB275Pwb3tvyPt2HDxB8Mi2MgH8Tz3AC83YYiz9LydsLNXEZJLHY" ], [- "7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ"+ "39TKsUQ5QpEL1wowc6GMUqak94ijirPuP69ooV3xsFmiKQX2dau" ], [- "2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP"+ "2UEJjT3dSdwc8dAo7oedPzznXceXCEsBbDfAvSymqpqDrkZMv7JBEUpLyhkghioYAWC9W4sKysry" ], [- "dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw"+ "7VmMEkphxCFSV1y659Th4dkk6x6bJS5eQvbt8rzUYKQyd6ACgwQ4vXHtXKFUwP2kW3XULipnHJdZ7" ], [- "HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX"+ "tc1qdlapns4zkn03juf2k9xwwpct209suj6mgcd9gh" ], [- "4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB"+ "bt1psa5eptk29c4jc9yumeseat3a0l5e2fpmw635za2p4gpwdnthueysxga9je" ], [- "Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ"+ "tb13w8c43lykfj3lvm9sgp6dsnfjla3d57cm83seykunf0ltxjc9lt2q4efm4d" ], [- "Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs"+ "bcrt1rjqr2tdkm" ], [- "6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ"+ "bc10lyxwnxa70l270e6fcmxr4x7dtgu2yvy7gzkurwxy4zhdvgaqrrn6pfg2flyhqzy5t5se8yu3" ], [- "giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4"+ "TB1QFDFM763VXVSUNZHQLPWC0Q8FG5LJX6ZN" ], [- "cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK"+ "bcrt1q60chha7wfwlau4kdr4mlvyeyc8mnnh9dhxk05e0hmrxcuhghefj36uwyha" ], [- "37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig"+ "bc1gmk9yu" ], [- "EsYbG4tWWWY45G31nox838qNdzksbPySWc"+ "tb1ly0q7p" ], [- "nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT"+ "bcrt1qdwttaw38uf42wxw40kwk3u8nguyTQH3hx6jmqp" ], [- "cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx"+ "bc1qtsvlht6730n04f2mpaj5vv8hrledn5n5ug8c79" ], [- "1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde"+ "tb1dclvmr" ], [- "2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU"+ "bcrt1q3fqvctqu48wsvggrt09vj0yk2gzzcscdp4h98u" ], [- "ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf"+ "bc1prklpq7tjcawg89cmwwqr3u5apwav36xa4zz56ady7crsllm6mpnqts7p86" ], [- "Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd"+ "tb1zkm58zyhxz3ffkfgsyprflg543slsl4c4" ], [- "2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED"+ "bcrt1snzr5kaypnfhpnjanrhd20fhqcjxm3hfh7dw9fu" ], [- "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"+ "2GgnYKqBGuA2Mm5GnrPsMTZR81xPhNtgMYoFUZngZGiobhCuUpCaTriUHRcgFreEekNdPAR17q8d" ], [- "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"+ "AZEah8d1EK362okRBS66e8SvdtYkrE8tsX" ], [- "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"+ "gep8xr77FyPW6zYP15RiV9W8nL6w2HyHB16cUDakfyDceMA6ZzUdhJjk2LPuLYHnLkBqkRTTi6z" ], [- "bc1rw5uspcuh"+ "2NDNP7GY59tTJPZTpbkprhM9SR99Nn5rUs7" ], [- "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"+ "2Csgzy2T287YAjeU5tFtt1nPshBZAUFQi4WtgaWyZGKSBNnKXHy2Tmxo8QK4Mfdds977ShcDWC5o" ], [- "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"+ "Kwjk3Vy6sdXMQDGWJzaWmqFxUNtWZCX1q4F4Kpt8jNNUoWJUUaTY" ], [- "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"+ "Svj8kk98bAS9V4L2crmxakbhmnPm3cJ1tJ4Je4yVzDreU8eSTFURS1SPYv5oWEQD8Q9VBDvx5uF" ], [- "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"+ "KNYsv6v9GtkGeD4WdQnBEJCrPKQm91PTxAbCfXr66LEd4JDmhPWC" ], [- "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"+ "2UJ2H2xvAeXmFKfQwMyDoSdQTTPFMNCT3SsoUafBWKzoGP3NsUK1buEgQZG38viyD53jgMdpqfT7" ], [- "bc1gmk9yu"+ "6aLMfayKF4TW4ecn5SEc8FExpyJA2peKxYRGZhes6tQ4NTTzuGy"+ ],+ [+ "7VP4FmcebU2thJns9MnXde7LWfuqR5vMizrAuUoq2GcJjzTyA4RHFcPVdZL8PLg1SbpSFdJrvLXoY5"+ ],+ [+ "tc1q5qdvt99uc92jyz663dtdpfpv6nr67ahmgwcpq2"+ ],+ [+ "bt1peu3ppd7x796sjjenp09r8cs22rhylqm9lhggk72qp8q22vzft0wq2a0x6j"+ ],+ [+ "tb1323z3lnz7dl3kd0nsuh6xy4he9almzl67anxgg3xdzkaxc9rwntlqdhdzd7"+ ],+ [+ "bcrt1r2gc42sky"+ ],+ [+ "bc10fd889x4hd54tqu2ewg9t4hhft2wl7m6x50av4uswzw46xe6as0xmltfg7vrjfkvm459vld7w"+ ],+ [+ "TB1QZY7V0F2AT3308YGGNGN66ULJTCN3RY6F"+ ],+ [+ "bcrt1qjg3cwht92znyw0l4r5rtctmls337nrc7g0ry9drjxmlecjd3atl3fake7c"+ ],+ [+ "bc1qmgf8xt8xkecl79k04mma3lz34gqep7hg4"+ ],+ [+ "TB1Q3F9WGNXE9ZMTTMDN5VKVKHYZ8Y0LCV72YV7V5LSXTJXEYHNHEHASLYL0TZ" ] ]
data/key_io_valid.json view
@@ -1,536 +1,610 @@ [ [- "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i",- "76a91465a16059864a2fdbc7c99a4723a8395bc6f188eb88ac",+ "1FsSia9rv4NeEwvJ2GvXrX7LyxYspbN2mo",+ "76a914a31c06bd463e3923bc1aadbde48b16976c08071788ac", {- "isPrivkey": false,- "chain": "main"+ "chain": "main",+ "isPrivkey": false } ], [- "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou",- "a91474f209f6ea907e2ea48f74fae05782ae8a66525787",+ "36j4NfKv6Akva9amjWrLG6MuSQym1GuEmm",+ "a914373b819a068f32b7a6b38b6b38729647cfde01c287", {- "isPrivkey": false,- "chain": "main"+ "chain": "main",+ "isPrivkey": false } ], [- "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",- "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",+ "mzK2FFDEhxqHcmrJw1ysqFkVyhUULo45hZ",+ "76a914ce28b26c57472737f5c3561a1761185bd8589a4388ac", {- "isPrivkey": false,- "chain": "test"+ "chain": "testnet4",+ "isPrivkey": false } ], [- "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",- "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",+ "2NC2hEhe28ULKAJkW5MjZ3jtTMJdvXmByvK",+ "a914ce0bba75891ff9ec60148d4bd4a09ee2dc5c933187", {- "isPrivkey": false,- "chain": "regtest"+ "chain": "testnet4",+ "isPrivkey": false } ], [- "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br",- "a9146349a418fc4578d10a372b54b45c280cc8c4382f87",+ "mww4LvqtTMKvmeQvizPz2EQv26xTneWrbg",+ "76a914b4110ba93ac54afc14da3bdd19614774a2d55d2988ac", {- "isPrivkey": false,- "chain": "test"+ "chain": "signet",+ "isPrivkey": false } ], [- "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr",- "eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19",+ "2N1r7aC69VHeE7yQJPDLi9T1PYq4wnwvjuT",+ "a9145e5a35ab44b3efaea5129ba22b88ba3e2976614587", {+ "chain": "signet",+ "isPrivkey": false+ }+ ],+ [+ "n4fajahJrAuKbN7uNsKjLjQkz9Qn5ewJXQ",+ "76a914fdeca3b08e38af53d7c4c60e3ad208ce5066441088ac",+ {+ "chain": "regtest",+ "isPrivkey": false+ }+ ],+ [+ "2MxFajLApXpYk4VodBSZSt7rw8y4ryABkfA",+ "a91436e9f191e0b75036a77f65e2eaa4752443233fbe87",+ {+ "chain": "regtest",+ "isPrivkey": false+ }+ ],+ [+ "5JuW2AMDYu4xVwRG9DZW18VbzQrGcd5RCgb99sS6ehJsNQXu5b9",+ "8f8943bf956de595665c38ffff23827e17c10cdc1c27a028caae6c9810626198",+ {+ "chain": "main", "isCompressed": false,- "isPrivkey": true,- "chain": "main"+ "isPrivkey": true } ], [- "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD",- "55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4",+ "L5nJeqKmpHp4P7F8ZYyjwc5a7P4d8EabuGAzfGJk7yC1BJyzNaEd",+ "ff778740f88ddcf102aeb81daee289c044c4a4571c4b6f287400f4b8e0b843f8", {+ "chain": "main", "isCompressed": true,- "isPrivkey": true,- "chain": "main"+ "isPrivkey": true } ], [- "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",- "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2",+ "92ZdE5HoLafywnTBbzPxbvRmp75pSfzvdU3XaZGh1cToipgdHVh",+ "80c32d81e91bdea04cd7a3819b32275fc3298af4c7ec87eb0099527d041ced5c", {+ "chain": "testnet4", "isCompressed": false,- "isPrivkey": true,- "chain": "test"+ "isPrivkey": true } ], [- "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",- "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2",+ "cV83kKisF3RQSvXbUCm9ox3kaz5JjEUBWcx8tNydfGJcyeUxuH47",+ "e0fcd4ce4e3d0e3de091f21415bb7cd011fac288c42020a879f28c2a4387df9b", {+ "chain": "testnet4",+ "isCompressed": true,+ "isPrivkey": true+ }+ ],+ [+ "92QuSnywrhsV7WPZChTgSQA23uSmj9MCEEno1eRBDG9sg8M29cX",+ "6cf636ed8ac1bab033b64f66feaba65f70e684731e3f39105605968d3a963801",+ {+ "chain": "signet", "isCompressed": false,- "isPrivkey": true,- "chain": "regtest"+ "isPrivkey": true } ], [- "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",- "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3",+ "cND53Dhp8eCZqG2ghe8YhSCGesXZ8fE5PGD1khrqNvEi4RBoXhEK",+ "12b5a10f3a11e708dc5412833c47ab7c368a21b9efe19293793ec879ce683018", {+ "chain": "signet", "isCompressed": true,- "isPrivkey": true,- "chain": "test"+ "isPrivkey": true } ], [- "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",- "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3",+ "91mn1wYKEB1zyof1VFm8tMtocZx1oBrKKRCu9GCpgZvPmBLEJjp",+ "18a86e5a6c6977ddba0daca7fba5190f67ba56ccdc1b3f31308972236c2e4776", {- "isCompressed": true,- "isPrivkey": true,- "chain": "regtest"+ "chain": "regtest",+ "isCompressed": false,+ "isPrivkey": true } ], [- "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ",- "76a9146d23156cbbdcc82a5a47eee4c2c7c583c18b6bf488ac",+ "cPisAUdLvqqAr6MYtXnrWvgvyUAwuNyuTvZkDGw6miPhZdaiSDNH",+ "3fdfec1371cedcdb8c190ca6ff8ad603f817edc0d93c2a687c7b36dd66e70f2a", {- "isPrivkey": false,- "chain": "main"+ "chain": "regtest",+ "isCompressed": true,+ "isPrivkey": true } ], [- "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy",- "a914fcc5460dd6e2487c7d75b1963625da0e8f4c597587",+ "bc1qvyq0cc6rahyvsazfdje0twl7ez82ndmuac2lhv",+ "00146100fc6343edc8c874496cb2f5bbfec88ea9b77c", {+ "chain": "main", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ",- "76a914f1d470f9b02370fdec2e6b708b08ac431bf7a5f788ac",+ "bc1qyucykdlhp62tezs0hagqury402qwhk589q80tqs5myh3rxq34nwqhkdhv7",+ "002027304b37f70e94bc8a0fbf500e0c957a80ebda87280ef58214d92f119811acdc", {+ "chain": "main", "isPrivkey": false,- "chain": "test"+ "tryCaseFlip": true } ], [- "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n",- "a914c579342c2c4c9220205e2cdc285617040c924a0a87",+ "bc1p83n3au0rjylefxq2nc2xh2y4jzz4pm6zxj4mw5pagdjjr2a9f36s6jjnnu",+ "51203c671ef1e3913f94980a9e146ba895908550ef4234abb7503d436521aba54c75", {+ "chain": "main", "isPrivkey": false,- "chain": "test"+ "tryCaseFlip": true } ], [- "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc",- "a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e",+ "bc1z2rksukkjr8",+ "520250ed", {- "isCompressed": false,- "isPrivkey": true,- "chain": "main"+ "chain": "main",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi",- "7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4",+ "tb1qcrh3yqn4nlleplcez2yndq2ry8h9ncg3qh7n54",+ "0014c0ef1202759fff90ff19128936814321ee59e111", {- "isCompressed": true,- "isPrivkey": true,- "chain": "main"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj",- "d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203",+ "tb1quyl9ujpgwr2chdzdnnalen48sup245vdfnh2jxhsuq3yx80rrwlq5hqfe4",+ "0020e13e5e482870d58bb44d9cfbfccea78702aad18d4ceea91af0e022431de31bbe", {- "isCompressed": false,- "isPrivkey": true,- "chain": "test"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN",- "a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9",+ "tb1p35n52jy6xkm4wd905tdy8qtagrn73kqdz73xe4zxpvq9t3fp50aqk3s6gz",+ "51208d2745489a35b75734afa2da43817d40e7e8d80d17a26cd4460b0055c521a3fa", {- "isCompressed": true,- "isPrivkey": true,- "chain": "test"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv",- "76a9147987ccaa53d02c8873487ef919677cd3db7a691288ac",+ "tb1rgv5m6uvdk3kc7qsuz0c79v88ycr5w4wa",+ "53104329bd718db46d8f021c13f1e2b0e726", {+ "chain": "testnet4", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks",- "a91463bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb87",+ "tb1q3vya2h5435jkugq2few7dmktlrwq4ejmfaw7kr",+ "00148b09d55e958d256e200a4e5de6eecbf8dc0ae65b", {+ "chain": "signet", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk",- "76a914ef66444b5b17f14e8fae6e7e19b045a78c54fd7988ac",+ "tb1qxkhrl2s6ttrclckldruea0e8anhrehffl8xv7t0pdyrzm08v2hyqy408nf",+ "002035ae3faa1a5ac78fe2df68f99ebf27ecee3cdd29f9cccf2de169062dbcec55c8", {+ "chain": "signet", "isPrivkey": false,- "chain": "test"+ "tryCaseFlip": true } ], [- "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o",- "a914c3e55fceceaa4391ed2a9677f4a4d34eacd021a087",+ "tb1pae5um27ahn8n73pgexe3kcwlp8dhswpn684h2k2w6t9a7w3eq65qephd5y",+ "5120ee69cdabddbccf3f4428c9b31b61df09db783833d1eb75594ed2cbdf3a3906a8", {+ "chain": "signet", "isPrivkey": false,- "chain": "test"+ "tryCaseFlip": true } ], [- "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9",- "e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252",+ "tb1rx9n9g37az8mu236e5jpxdt0m67y4fuq8rhs0ss3djnm0kscfrwvq0ntlyg",+ "532031665447dd11f7c54759a48266adfbd78954f0071de0f8422d94f6fb43091b98", {- "isCompressed": false,- "isPrivkey": true,- "chain": "main"+ "chain": "signet",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT",- "8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c",+ "bcrt1qdavt4j2sd7dlhqsavtnfxvzppw6k7qy97tmnu9",+ "00146f58bac9506f9bfb821d62e69330410bb56f0085", {- "isCompressed": true,- "isPrivkey": true,- "chain": "main"+ "chain": "regtest",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo",- "44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52",+ "bcrt1qan8gntac7z7me2ejt4hpru42ad2f759fmy0m3ejvs98656znv7eqga4uhv",+ "0020ecce89afb8f0bdbcab325d6e11f2aaeb549f50a9d91fb8e64c814faa685367b2", {- "isCompressed": false,- "isPrivkey": true,- "chain": "test"+ "chain": "regtest",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7",- "d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69",+ "bcrt1pfwxjqvtt4tcxrtdluukfmy2dv7xd2qzdfy6kajv5nwn4yam3wxkq3553uh",+ "51204b8d20316baaf061adbfe72c9d914d678cd5004d49356ec9949ba752777171ac", {- "isCompressed": true,- "isPrivkey": true,- "chain": "test"+ "chain": "regtest",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu",- "76a914adc1cc2081a27206fae25792f28bbc55b831549d88ac",+ "bcrt1sx6p8njlx7h9mc2agz4yg82dzne23050ncq72cneeecez2pst8mahn8xecsf8g6hzx94420",+ "6028368279cbe6f5cbbc2ba8154883a9a29e5517d1f3c03cac4f39ce3225060b3efb799cd9c412746ae2", {+ "chain": "regtest", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk",- "a914188f91a931947eddd7432d6e614387e32b24470987",+ "1FjL87pn8ky6Vbavd1ZHeChRXtoxwRGCRd",+ "76a914a19331b7b2627e663e25a7b001e4c0dcc5e21bc788ac", {- "isPrivkey": false,- "chain": "main"+ "chain": "main",+ "isPrivkey": false } ], [- "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H",- "76a9141694f5bc1a7295b600f40018a618a6ea48eeb49888ac",+ "3BZECeAH8gSKkjrTx8PwMrNQBLG18yHpvf",+ "a9146c382dcdf5b284760c8e3fead91f7422cd76aa8787", {- "isPrivkey": false,- "chain": "test"+ "chain": "main",+ "isPrivkey": false } ], [- "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN",- "a9143b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f387",+ "n4YNbYuFdPwFrxSP8sjHFbAhUbLMUiY9jE",+ "76a914fc8f9851f3c1e4719cd0b8e4816dd4e88c72e52888ac", {- "isPrivkey": false,- "chain": "test"+ "chain": "testnet4",+ "isPrivkey": false } ], [- "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR",- "091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0",+ "2NAeQVZayzVFAtgeC3iYJsjpjWDmsDph71A",+ "a914bedc797342c03fd7a346c4c7857ca03d467013b687", {- "isCompressed": false,- "isPrivkey": true,- "chain": "main"+ "chain": "testnet4",+ "isPrivkey": false } ], [- "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8",- "ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af",+ "mnCBpkNMJEJLehgdEkzSo2eioniyJMxLpZ",+ "76a914493c455551e48a1423263b62b127b436106a685488ac", {- "isCompressed": true,- "isPrivkey": true,- "chain": "main"+ "chain": "signet",+ "isPrivkey": false } ], [- "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",- "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856",+ "2N5sNHomeNJDZv67AcFx9ES7FBZY4jx9KDA",+ "a9148a776a0f34d56b63e7c595f2b205dbe1c393617a87", {- "isCompressed": false,- "isPrivkey": true,- "chain": "test"+ "chain": "signet",+ "isPrivkey": false } ], [- "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",- "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856",+ "mfhE6jAUwjUDNZhaX1PAsDTKfneQF2Nshc",+ "76a91401f15a4cc063dae4f4d56b89bfbc8bcc9ae5387c88ac", {- "isCompressed": false,- "isPrivkey": true,- "chain": "regtest"+ "chain": "regtest",+ "isPrivkey": false } ], [- "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA",- "e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef",+ "2MxNm1VHyVU4RuP3u1c1v5aQLk2dQjwy1Qk",+ "a91438456f7c076356abadcc67b92ad777eb20fb9f8887", {- "isCompressed": true,- "isPrivkey": true,- "chain": "test"+ "chain": "regtest",+ "isPrivkey": false } ], [- "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4",- "76a914c4c1b72491ede1eedaca00618407ee0b772cad0d88ac",+ "5HsL2nZuEebU5nM3RxNVQD9GcAnvNMahqQskf4fkqHe54zwd14e",+ "06e8649790a90615a46d22dd762e0c42615336745356c2e16147c0f3d46b40d5", {- "isPrivkey": false,- "chain": "main"+ "chain": "main",+ "isCompressed": false,+ "isPrivkey": true } ], [- "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y",- "a914f6fe69bcb548a829cce4c57bf6fff8af3a5981f987",+ "KwuVvu6hsuEMHrfFWJQV64tRrWX3QzqHH18JuAHYqYV6dqBvNKxd",+ "147804bf8a0dfff35939a611c7f5a60ac107f33f33d6059f273d2079ab1d90f2", {- "isPrivkey": false,- "chain": "main"+ "chain": "main",+ "isCompressed": true,+ "isPrivkey": true } ], [- "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6",- "76a914261f83568a098a8638844bd7aeca039d5f2352c088ac",+ "921M1RNxghFcsVGqAJksQVbSgx36Yz4u6vebfz1wDujNvgNt93B",+ "3777b341c45e2a9b9bf6bfb71dc7d129f64f1b9406ed4f93ade8f56065f1b732", {- "isPrivkey": false,- "chain": "test"+ "chain": "testnet4",+ "isCompressed": false,+ "isPrivkey": true } ], [- "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda",- "a914e930e1834a4d234702773951d627cce82fbb5d2e87",+ "cNEnbfF2fcxmmCLWqMAaq6fxJvVkwMbyU3kCbpQznz4Z1j6TZDGb",+ "1397b0d4a03e1ab2c54dd9af99ce1ecbfb90c80a58886da95e1181a55703d96b", {- "isPrivkey": false,- "chain": "test"+ "chain": "testnet4",+ "isCompressed": true,+ "isPrivkey": true } ], [- "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg",- "d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0",+ "93BcpCMKPmFCuY8bqS4k3HFrhJ1Afxi4uSsEeJFvX86GYW7PC7W",+ "d27d1b6ef55ca2e4d475b5276f2dbb85f7a6459dceeb89c67b776fd3bb974452", {+ "chain": "signet", "isCompressed": false,- "isPrivkey": true,- "chain": "main"+ "isPrivkey": true } ], [- "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi",- "b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3",+ "cUtwbyxoL1owPxUafgH2meEpydeywjhnTYv2mJaFHHchz39AaEgy",+ "da3ed4ef1647e1733ec076919cab6156077ed9532e7c365acc425747e198b3e1", {+ "chain": "signet", "isCompressed": true,- "isPrivkey": true,- "chain": "main"+ "isPrivkey": true } ], [- "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys",- "037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb",+ "927zPWny2SiNaUmHF5NnGQXQWDwbByfFzXGgu88j91ZoutSosvE",+ "468e0284f230153db8687d8ec23db079a5b67d72ca04174b3867b13e4ea9945e", {+ "chain": "regtest", "isCompressed": false,- "isPrivkey": true,- "chain": "test"+ "isPrivkey": true } ], [- "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw",- "6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de",+ "cRez45VGSp5EXNqm89K3NJJPSKKapJg5Kbw3atxr2337x2gtgYed",+ "798d87586cffbe8c545ab374454e403b1eb831501ebe89f3c3b02f3137bd7b46", {+ "chain": "regtest", "isCompressed": true,- "isPrivkey": true,- "chain": "test"+ "isPrivkey": true } ], [- "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r",- "76a9145eadaf9bb7121f0f192561a5a62f5e5f5421029288ac",+ "bc1qhxt04s5xnpy0kxw4x99n5hpdf5pmtzpqs52es2",+ "0014b996fac2869848fb19d5314b3a5c2d4d03b58820", {+ "chain": "main", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3",- "a9143f210e7277c899c3a155cc1c90f4106cbddeec6e87",+ "bc1qgc9ljrvdf2e0zg9rmmq86xklqwfys7r6wptjlacdgrcdc7sa6ggqu4rrxf",+ "0020460bf90d8d4ab2f120a3dec07d1adf039248787a70572ff70d40f0dc7a1dd210", {+ "chain": "main", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "myoqcgYiehufrsnnkqdqbp69dddVDMopJu",- "76a914c8a3c2a09a298592c3e180f02487cd91ba3400b588ac",+ "bc1pve739yap4uxjvfk0jrey69078u0gasm2nwvv483ec6zkzulgw9xqu4w9fd",+ "5120667d1293a1af0d2626cf90f24d15fe3f1e8ec36a9b98ca9e39c6856173e8714c", {+ "chain": "main", "isPrivkey": false,- "chain": "test"+ "tryCaseFlip": true } ], [- "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C",- "a91499b31df7c9068d1481b596578ddbb4d3bd90baeb87",+ "bc1zmjtqxkzs89",+ "5202dc96", {+ "chain": "main", "isPrivkey": false,- "chain": "test"+ "tryCaseFlip": true } ], [- "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4",- "c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae",+ "tb1ql4k5ayv7p7w0t0ge7tpntgpkgw53g2payxkszr",+ "0014fd6d4e919e0f9cf5bd19f2c335a03643a914283d", {- "isCompressed": false,- "isPrivkey": true,- "chain": "main"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2",- "07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd",+ "tb1q9jx3x2qqdpempxrcfgyrkjd5fzeacaqj4ua7cs7fe2sfd2wdaueq5wn26y",+ "00202c8d1328006873b098784a083b49b448b3dc7412af3bec43c9caa096a9cdef32", {- "isCompressed": true,- "isPrivkey": true,- "chain": "main"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV",- "ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801",+ "tb1pdswckwd9ym5yf5eyzg8j4jjwnzla8y0tf9cp7aasfkek0u29sz9qfr00yf",+ "51206c1d8b39a526e844d324120f2aca4e98bfd391eb49701f77b04db367f145808a", {- "isCompressed": false,- "isPrivkey": true,- "chain": "test"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h",- "0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c",+ "tb1r0ecpfxg2udhtc556gqrpwwhk4sw3f0kc",+ "53107e7014990ae36ebc529a4006173af6ac", {- "isCompressed": true,- "isPrivkey": true,- "chain": "test"+ "chain": "testnet4",+ "isPrivkey": false,+ "tryCaseFlip": true } ], [- "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE",- "76a9141ed467017f043e91ed4c44b4e8dd674db211c4e688ac",+ "tb1q6mwf89hnqhlu8txjgjfs4s7p93ugffn3k062ll",+ "0014d6dc9396f305ffc3acd244930ac3c12c7884a671", {+ "chain": "signet", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G",- "a9145ece0cadddc415b1980f001785947120acdb36fc87",+ "tb1qafrjalu4d73dql0czau9j6z422434kef235mzljf48ckd5xz3sys09jm97",+ "0020ea472eff956fa2d07df8177859685552ab1adb295469b17e49a9f166d0c28c09", {+ "chain": "signet", "isPrivkey": false,- "chain": "main"+ "tryCaseFlip": true } ], [- "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",- "0014751e76e8199196d454941c45d1b3a323f1433bd6",+ "tb1pwst9qszjrhuv2e7as0flcq9gm698v6gdxzz9e87p07s8rssdx3zqklm3vf",+ "512074165040521df8c567dd83d3fc00a8de8a76690d30845c9fc17fa071c20d3444", {+ "chain": "signet", "isPrivkey": false,- "chain": "main", "tryCaseFlip": true } ], [- "bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080",- "0014751e76e8199196d454941c45d1b3a323f1433bd6",+ "tb1r3ss76jtsuxe8c8c8lxsehnpak55ylrgr345pww076l536ahjr6jsydamx3",+ "53208c21ed4970e1b27c1f07f9a19bcc3db5284f8d038d681739fed7e91d76f21ea5", {+ "chain": "signet", "isPrivkey": false,+ "tryCaseFlip": true+ }+ ],+ [+ "bcrt1q65nhlm4hf2ptg3t264al57p7wjxj2c3s6kyt83",+ "0014d5277feeb74a82b4456ad57bfa783e748d256230",+ { "chain": "regtest",+ "isPrivkey": false, "tryCaseFlip": true } ], [- "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",- "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",+ "bcrt1qawvc90lpytw3z3k9etdx54l0exq5f5sqfzu5e45kjnl6slwayeeqx2dyac",+ "0020eb9982bfe122dd1146c5cada6a57efc98144d20048b94cd69694ffa87ddd2672", {+ "chain": "regtest", "isPrivkey": false,- "chain": "test", "tryCaseFlip": true } ], [- "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",- "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",+ "bcrt1p39a4s4vdcw9kqa8w2t0rp7aj8kfxyw7mce5sk5d70x6wnnmpvt7skf2kxy",+ "5120897b58558dc38b6074ee52de30fbb23d92623bdbc6690b51be79b4e9cf6162fd", {+ "chain": "regtest", "isPrivkey": false,- "chain": "main",- "tryCaseFlip": true,- "disabled": true+ "tryCaseFlip": true } ], [- "bc1sw50qa3jx3s",- "6002751e",+ "bcrt1s489d9fhmyel0vzfqsrmew4x7r80asuqesm5hgqacy35daflcyufh3j8cgdtflvt99ph05m",+ "6028a9cad2a6fb267ef6092080f79754de19dfd8701986e97403b82468dea7f8271378c8f843569fb165", {+ "chain": "regtest", "isPrivkey": false,+ "tryCaseFlip": true+ }+ ],+ [+ "1G9A9j6W8TLuh6dEeVwWeyibK1Uc5MfVFV",+ "76a914a614da54daacdb8861f451a0b7e3c27cdf8a099e88ac",+ { "chain": "main",- "tryCaseFlip": true,- "disabled": true+ "isPrivkey": false } ], [- "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",- "5210751e76e8199196d454941c45d1b3a323",+ "33GA3ZXbw5o5HeUrBEaqkWXFYYZmdxGRRP",+ "a914113ca1afeb49ff3abf176ffa19c2a2b4df19712a87", {- "isPrivkey": false, "chain": "main",- "tryCaseFlip": true,- "disabled": true+ "isPrivkey": false } ], [- "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",- "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",+ "mwgS2HRbjyfYxFnR1nF9VKLvmdgMfFBmGq",+ "76a914b14ce7070b53cb0e4b5b5f6e253e876990aeca2e88ac", {- "isPrivkey": false,- "chain": "test",- "tryCaseFlip": true+ "chain": "testnet4",+ "isPrivkey": false } ], [- "bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",- "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",+ "2MwBVrJQ76BdaGD76CTmou8cZzQYLpe4NqU",+ "a9142b2c149cde619eae3d7fe995243b76a3417541aa87", {- "isPrivkey": false,- "chain": "regtest",- "tryCaseFlip": true+ "chain": "testnet4",+ "isPrivkey": false+ }+ ],+ [+ "mfnJ8tEkqKNFE5YaHTXFxyHk2mnDK2fvDh",+ "76a91402e6cd77e649ad8b281271f158fc964ca3f66cb088ac",+ {+ "chain": "signet",+ "isPrivkey": false+ }+ ],+ [+ "2My83D67ir7K8PPzeT6mE2oth3ZwNTVRS9F",+ "a9144074d84d32ff62da7b1b3c61925b934bfeb34b0587",+ {+ "chain": "signet",+ "isPrivkey": false } ] ]
+ data/psbt_complex_core32.json view
@@ -0,0 +1,18 @@+{+ "initial_psbt": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQkBBQEBAfsEAgAAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAAAAAABEAT9////AAEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EAQAAAAEQBP3///8AAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQCAAAAARAE/f///wABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAMAAAABEAT9////AAEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EBAAAAAEQBP3///8AAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQFAAAAARAE/f///wABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAYAAAABEAT9////AAEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EBwAAAAEQBP3///8AAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQIAAAAARAE/f///wABAwjAQcgXAAAAAAEEFgAUTpbJk/Pp7+I9+2XU/5tnno3B+iAA",+ "miner_psbt": "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",+ "p2pkh_psbt": "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",+ "p2sh_pk_psbt": "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",+ "p2sh_ms_1_psbt": "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",+ "p2sh_ms_2_psbt": "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",+ "p2sh_wsh_pk_psbt": "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",+ "p2sh_wsh_ms_1_psbt": "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",+ "p2sh_wsh_ms_2_psbt": "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",+ "p2wpkh_psbt": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQkBBQEBAfsEAgAAAAABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAAAAAABEAT9////AAEA/WkBAgAAAAE/Pl1wMQH0ucUcNVfR2TC0N6vMUYA/acbzg53ICHQiQgAAAAAA/f///wmA8PoCAAAAABepFCVCsDoJ6h32WmHXasJvJkFzlrVgh4Dw+gIAAAAAIgAgTU5U71y8tM40msvoBwsFbhsQDsLy2CxTpj5dpwMX7DaA8PoCAAAAABepFPmAIH4DiiBuc4FhS7pyxC4U9Dcgh85eLhIBAAAAFgAUOALpJK38yRF36+SrQQ2wFdt4Yo6A8PoCAAAAABYAFCPq443DSm6uMD+YcaiNvVrKUcowgPD6AgAAAAAZdqkU0wGs7kU6SaYV2Z/+Pi5QY764xp+IrIDw+gIAAAAAF6kUyNQFyIeQ160SFwL8tpMay+FmHwSHgPD6AgAAAAAXqRQZ6bAbRl/YgAzmDfKGkhEuedvSPYeA8PoCAAAAACIAIPWUuKjO3wzSx5sOvvoSmT54cT14qzNG+oNFNzc+D4mEZQAAAAEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EAQAAAAEQBP3///8AAQD9aQECAAAAAT8+XXAxAfS5xRw1V9HZMLQ3q8xRgD9pxvODncgIdCJCAAAAAAD9////CYDw+gIAAAAAF6kUJUKwOgnqHfZaYddqwm8mQXOWtWCHgPD6AgAAAAAiACBNTlTvXLy0zjSay+gHCwVuGxAOwvLYLFOmPl2nAxfsNoDw+gIAAAAAF6kU+YAgfgOKIG5zgWFLunLELhT0NyCHzl4uEgEAAAAWABQ4AukkrfzJEXfr5KtBDbAV23hijoDw+gIAAAAAFgAUI+rjjcNKbq4wP5hxqI29WspRyjCA8PoCAAAAABl2qRTTAazuRTpJphXZn/4+LlBjvrjGn4isgPD6AgAAAAAXqRTI1AXIh5DXrRIXAvy2kxrL4WYfBIeA8PoCAAAAABepFBnpsBtGX9iADOYN8oaSES5529I9h4Dw+gIAAAAAIgAg9ZS4qM7fDNLHmw6++hKZPnhxPXirM0b6g0U3Nz4PiYRlAAAAAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQCAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABAR/OXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQDAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABAR+A8PoCAAAAABYAFCPq443DSm6uMD+YcaiNvVrKUcowIgIC0yKASxiJ7lhxBV57+OAqJ+5oGjeu7ao7KfNpl51x4alHMEQCIDWdP04guo7cT2M8spo+153LrZnW6HZZ0WUXJjB+ZdGtAiBLBkVUbIGN1xyL8poYpXB/DDbBJXC1bz4tpyFbiMNcQgEiBgLTIoBLGInuWHEFXnv44Con7mgaN67tqjsp82mXnXHhqQQj6uONAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQEAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAUAAAABEAT9////AAEA/WkBAgAAAAE/Pl1wMQH0ucUcNVfR2TC0N6vMUYA/acbzg53ICHQiQgAAAAAA/f///wmA8PoCAAAAABepFCVCsDoJ6h32WmHXasJvJkFzlrVgh4Dw+gIAAAAAIgAgTU5U71y8tM40msvoBwsFbhsQDsLy2CxTpj5dpwMX7DaA8PoCAAAAABepFPmAIH4DiiBuc4FhS7pyxC4U9Dcgh85eLhIBAAAAFgAUOALpJK38yRF36+SrQQ2wFdt4Yo6A8PoCAAAAABYAFCPq443DSm6uMD+YcaiNvVrKUcowgPD6AgAAAAAZdqkU0wGs7kU6SaYV2Z/+Pi5QY764xp+IrIDw+gIAAAAAF6kUyNQFyIeQ160SFwL8tpMay+FmHwSHgPD6AgAAAAAXqRQZ6bAbRl/YgAzmDfKGkhEuedvSPYeA8PoCAAAAACIAIPWUuKjO3wzSx5sOvvoSmT54cT14qzNG+oNFNzc+D4mEZQAAAAEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EBgAAAAEQBP3///8AAQD9aQECAAAAAT8+XXAxAfS5xRw1V9HZMLQ3q8xRgD9pxvODncgIdCJCAAAAAAD9////CYDw+gIAAAAAF6kUJUKwOgnqHfZaYddqwm8mQXOWtWCHgPD6AgAAAAAiACBNTlTvXLy0zjSay+gHCwVuGxAOwvLYLFOmPl2nAxfsNoDw+gIAAAAAF6kU+YAgfgOKIG5zgWFLunLELhT0NyCHzl4uEgEAAAAWABQ4AukkrfzJEXfr5KtBDbAV23hijoDw+gIAAAAAFgAUI+rjjcNKbq4wP5hxqI29WspRyjCA8PoCAAAAABl2qRTTAazuRTpJphXZn/4+LlBjvrjGn4isgPD6AgAAAAAXqRTI1AXIh5DXrRIXAvy2kxrL4WYfBIeA8PoCAAAAABepFBnpsBtGX9iADOYN8oaSES5529I9h4Dw+gIAAAAAIgAg9ZS4qM7fDNLHmw6++hKZPnhxPXirM0b6g0U3Nz4PiYRlAAAAAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQHAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAgAAAABEAT9////AAEDCMBByBcAAAAAAQQWABROlsmT8+nv4j37ZdT/m2eejcH6IAA=",+ "p2wsh_pk_psbt": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQkBBQEBAfsEAgAAAAABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAAAAAABEAT9////AAEA/WkBAgAAAAE/Pl1wMQH0ucUcNVfR2TC0N6vMUYA/acbzg53ICHQiQgAAAAAA/f///wmA8PoCAAAAABepFCVCsDoJ6h32WmHXasJvJkFzlrVgh4Dw+gIAAAAAIgAgTU5U71y8tM40msvoBwsFbhsQDsLy2CxTpj5dpwMX7DaA8PoCAAAAABepFPmAIH4DiiBuc4FhS7pyxC4U9Dcgh85eLhIBAAAAFgAUOALpJK38yRF36+SrQQ2wFdt4Yo6A8PoCAAAAABYAFCPq443DSm6uMD+YcaiNvVrKUcowgPD6AgAAAAAZdqkU0wGs7kU6SaYV2Z/+Pi5QY764xp+IrIDw+gIAAAAAF6kUyNQFyIeQ160SFwL8tpMay+FmHwSHgPD6AgAAAAAXqRQZ6bAbRl/YgAzmDfKGkhEuedvSPYeA8PoCAAAAACIAIPWUuKjO3wzSx5sOvvoSmT54cT14qzNG+oNFNzc+D4mEZQAAAAEBK4Dw+gIAAAAAIgAgTU5U71y8tM40msvoBwsFbhsQDsLy2CxTpj5dpwMX7DYiAgMRHnLItYKx0TGdv6joFRXODEqBej/mGhevf+YICqN5fkcwRAIgDimVg2SvJW41L8wvBFNBZUVSWtODhizVt1JV2EwEXxICIGyjRQPQrP3r8TPCZv88dcetA9vkg20SxdXEHPtoT6/TAQEFIyEDER5yyLWCsdExnb+o6BUVzgxKgXo/5hoXr3/mCAqjeX6sIgYDER5yyLWCsdExnb+o6BUVzgxKgXo/5hoXr3/mCAqjeX4ECMXuzQEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EAQAAAAEQBP3///8AAQD9aQECAAAAAT8+XXAxAfS5xRw1V9HZMLQ3q8xRgD9pxvODncgIdCJCAAAAAAD9////CYDw+gIAAAAAF6kUJUKwOgnqHfZaYddqwm8mQXOWtWCHgPD6AgAAAAAiACBNTlTvXLy0zjSay+gHCwVuGxAOwvLYLFOmPl2nAxfsNoDw+gIAAAAAF6kU+YAgfgOKIG5zgWFLunLELhT0NyCHzl4uEgEAAAAWABQ4AukkrfzJEXfr5KtBDbAV23hijoDw+gIAAAAAFgAUI+rjjcNKbq4wP5hxqI29WspRyjCA8PoCAAAAABl2qRTTAazuRTpJphXZn/4+LlBjvrjGn4isgPD6AgAAAAAXqRTI1AXIh5DXrRIXAvy2kxrL4WYfBIeA8PoCAAAAABepFBnpsBtGX9iADOYN8oaSES5529I9h4Dw+gIAAAAAIgAg9ZS4qM7fDNLHmw6++hKZPnhxPXirM0b6g0U3Nz4PiYRlAAAAAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQCAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABAR/OXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQDAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABAR+A8PoCAAAAABYAFCPq443DSm6uMD+YcaiNvVrKUcowAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQEAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAUAAAABEAT9////AAEA/WkBAgAAAAE/Pl1wMQH0ucUcNVfR2TC0N6vMUYA/acbzg53ICHQiQgAAAAAA/f///wmA8PoCAAAAABepFCVCsDoJ6h32WmHXasJvJkFzlrVgh4Dw+gIAAAAAIgAgTU5U71y8tM40msvoBwsFbhsQDsLy2CxTpj5dpwMX7DaA8PoCAAAAABepFPmAIH4DiiBuc4FhS7pyxC4U9Dcgh85eLhIBAAAAFgAUOALpJK38yRF36+SrQQ2wFdt4Yo6A8PoCAAAAABYAFCPq443DSm6uMD+YcaiNvVrKUcowgPD6AgAAAAAZdqkU0wGs7kU6SaYV2Z/+Pi5QY764xp+IrIDw+gIAAAAAF6kUyNQFyIeQ160SFwL8tpMay+FmHwSHgPD6AgAAAAAXqRQZ6bAbRl/YgAzmDfKGkhEuedvSPYeA8PoCAAAAACIAIPWUuKjO3wzSx5sOvvoSmT54cT14qzNG+oNFNzc+D4mEZQAAAAEOIMJi6QRo1JwW6r9Kr87PLtmwKnCwUESH0W8cGHLOG6F3AQ8EBgAAAAEQBP3///8AAQD9aQECAAAAAT8+XXAxAfS5xRw1V9HZMLQ3q8xRgD9pxvODncgIdCJCAAAAAAD9////CYDw+gIAAAAAF6kUJUKwOgnqHfZaYddqwm8mQXOWtWCHgPD6AgAAAAAiACBNTlTvXLy0zjSay+gHCwVuGxAOwvLYLFOmPl2nAxfsNoDw+gIAAAAAF6kU+YAgfgOKIG5zgWFLunLELhT0NyCHzl4uEgEAAAAWABQ4AukkrfzJEXfr5KtBDbAV23hijoDw+gIAAAAAFgAUI+rjjcNKbq4wP5hxqI29WspRyjCA8PoCAAAAABl2qRTTAazuRTpJphXZn/4+LlBjvrjGn4isgPD6AgAAAAAXqRTI1AXIh5DXrRIXAvy2kxrL4WYfBIeA8PoCAAAAABepFBnpsBtGX9iADOYN8oaSES5529I9h4Dw+gIAAAAAIgAg9ZS4qM7fDNLHmw6++hKZPnhxPXirM0b6g0U3Nz4PiYRlAAAAAQ4gwmLpBGjUnBbqv0qvzs8u2bAqcLBQRIfRbxwYcs4boXcBDwQHAAAAARAE/f///wABAP1pAQIAAAABPz5dcDEB9LnFHDVX0dkwtDerzFGAP2nG84OdyAh0IkIAAAAAAP3///8JgPD6AgAAAAAXqRQlQrA6Ceod9lph12rCbyZBc5a1YIeA8PoCAAAAACIAIE1OVO9cvLTONJrL6AcLBW4bEA7C8tgsU6Y+XacDF+w2gPD6AgAAAAAXqRT5gCB+A4ogbnOBYUu6csQuFPQ3IIfOXi4SAQAAABYAFDgC6SSt/MkRd+vkq0ENsBXbeGKOgPD6AgAAAAAWABQj6uONw0purjA/mHGojb1aylHKMIDw+gIAAAAAGXapFNMBrO5FOkmmFdmf/j4uUGO+uMafiKyA8PoCAAAAABepFMjUBciHkNetEhcC/LaTGsvhZh8Eh4Dw+gIAAAAAF6kUGemwG0Zf2IAM5g3yhpIRLnnb0j2HgPD6AgAAAAAiACD1lLiozt8M0sebDr76Epk+eHE9eKszRvqDRTc3Pg+JhGUAAAABDiDCYukEaNScFuq/Sq/Ozy7ZsCpwsFBEh9FvHBhyzhuhdwEPBAgAAAABEAT9////AAEDCMBByBcAAAAAAQQWABROlsmT8+nv4j37ZdT/m2eejcH6IAA=",+ "p2wsh_ms_1_psbt": "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",+ "p2wsh_ms_2_psbt": "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",+ "combined_psbt": "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",+ "complete_psbt": "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",+ "final_tx": "02000000000109c262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba17700000000232200208ef92d01ee4d21a0fe373a9193f54a6ca6dd7e6ea372419eff61f4df4ce878a5fdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba1770100000000fdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba17702000000232200206915504d39daff3ee46d0344a232b5b5f1cf6d8edef28d5c56e5eb587a05f109fdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba1770300000000fdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba1770400000000fdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba177050000006a47304402206df6b93d4582004bd193aa991806c438ce6ed701112cfb72a7104e5d588d671402202b7eba2a30572520e84e6dd85965fcc90988be6d6d74a1a37a476e6663d6141d012103e4a112141aa2d01dfb3d296525f141f4c7286811899c251ccaeec6b24502be2bfdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba17706000000fc0047304402205803f8eafce9801dff0d8bb5bbf63fc9854901d6e9d8b135013f3194b91640a702204f6080204c524eafbe3dc1631bd051d972220cad85fa837e1cdd0e35da7e94300147304402204b0d0b2771cecc0307c845f35a87058cba6ac5f76462d519ed0d57aac5b6b171022001921987ac79f7a5d4aa301fca5087bc8199b37d653b164cf465cb44bd94395d014c69522102866d95fc2dd854f4f10b47067d53699c15851d739b6592f6f73b84d35412ac4821030f6707089f90b5e971d2df0ed4c71ca714f780abe2ff6bdc9aa160a21c97608421037a5707a94eb6a2a1ca472482a194022d6f2cc93c338880c28107a8ea6e408c6753aefdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba177070000006c47304402205cce9df7bbb5591f8aef95683b0de12a262a3155ddf13f63518af51c58f52f09022068f557ee7c83db340c803f7e3adf40dc1d687300707f9ddbf8ac303f8c0836d801232103db0fb9dfe845fcb75e7ea69860dd2b62b306c3852f2f689fc900f1dc032229eeacfdffffffc262e90468d49c16eabf4aafcecf2ed9b02a70b0504487d16f1c1872ce1ba1770800000000fdffffff01c041c817000000001600144e96c993f3e9efe23dfb65d4ff9b679e8dc1fa2002473044022074f882b6b6ad42d7456cfc0411a35010e1cfaadb3b9c287bf4dd0a927f86a15f02201025945dceb94820944015de3d504c312254fc38f6785aeb227710641a02253c012321030742cfccedddaa05d6ecbdeb28600502ad1e206d04f1ecfc043d7943edf65b5eac0247304402200e29958364af256e352fcc2f0453416545525ad383862cd5b75255d84c045f1202206ca34503d0acfdebf133c266ff3c75c7ad03dbe4836d12c5d5c41cfb684fafd301232103111e72c8b582b1d1319dbfa8e81515ce0c4a817a3fe61a17af7fe6080aa3797eac0400473044022058e4e17d7158b40fbc45e9a380ec63c9b6561bd1774be92cecd4475e9a37f47602203e5eea2e3ef2655f89ac30fa3f73943e3bee68508ca8eee6c66606b73d6dec6b0147304402200b580c4d272c815c6d284b2f641b5bccdbe84b22a1fa16e192d15af326a92cab02206ea42679e631d12d8670290fdbf67ff75ba1dd69093802f021277076e50270e701695221024a2b2867b8001c837f2f630d8586ce3a44ac5dd97cbcd6cea3643cf4bea7b8c6210397b1bde16f21191a65e16e1f46a4551de996bffa1e4eb3f0f3f1746eb480ae232103dd416cd596de2c2ca0f6625980937df55fec8a249334619015b5eda94102919653ae0247304402205332257a5cbbc59b68f3e4047aaf443a29f1e11b0139314e5be20f6e71b1015c0220058b7ab57781dd49e9260dc9fc2d5ad14689cc49898a403bb52c16a26bfba6580121033b78899278a2a7e673b8c00c39eaf3e96360d3b1feccc31af855a93482c07d1f024730440220359d3f4e20ba8edc4f633cb29a3ed79dcbad99d6e87659d1651726307e65d1ad02204b0645546c818dd71c8bf29a18a5707f0c36c12570b56f3e2da7215b88c35c42012102d322804b1889ee5871055e7bf8e02a27ee681a37aeedaa3b29f369979d71e1a900000004004730440220452f1fc7a60738f533b7705048cc5a2fa7854e5b138f65f3f9b348964c67be02022075686c7832c39813f67a0bfd353469c82b0db3f62559c2228d4b0c039910182a014730440220133157c86687fd769fbc7f8ccad3cae323e3e7bae207e014a8ae5eec5d45cbda022065fe3abe7398316caab39f7308be55a4ef76636c969c1cb3097544b59b16b0c101695221026589320adb556a333eb2234d1836f571ed3362a27ac99b348dfba25303b6092821027e1606d96721baccdb0b0212c2a551cd0348305929d7e2eb66950c02bc2284bb2102ecf5876024a2778099ee8499119272c424080e04f80e10172d861c7844a5d77353ae00000000"+}
+ data/slip39_split.json view
@@ -0,0 +1,324 @@+[+ {+ "description": "1-of-1, 128 bits, no passphrase",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 1,+ 1+ ]+ ]+ },+ "identifier": 7945,+ "extendable": true,+ "iterationExponent": 0,+ "passphrase": "",+ "masterSecret": "4142434445464748494a4b4c4d4e4f50",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [],+ "digest": ""+ }+ ],+ "mnemonics": [+ [+ "duckling evil academic academic cards jacket national hairy ancestor crunch muscle parcel award peanut guitar dryer eclipse peasant resident fact"+ ]+ ]+ },+ {+ "description": "2-of-3, 128 bits, passphrase TREZOR",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 2,+ 3+ ]+ ]+ },+ "identifier": 42,+ "extendable": true,+ "iterationExponent": 1,+ "passphrase": "TREZOR",+ "masterSecret": "4142434445464748494a4b4c4d4e4f50",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [],+ "digest": "698c2ccb37ff81b7f5603f9b"+ }+ ],+ "mnemonics": [+ [+ "acid fawn academic acid club member living lawsuit sunlight pancake market lend peasant sunlight glad simple infant organize grant infant",+ "acid fawn academic agency bracelet transfer march argue wits exact dough parcel animal traveler dictate river military salt satisfy payment",+ "acid fawn academic always advocate either wine oasis solution goat toxic injury radar romantic shrimp check warmth fangs ending mineral"+ ]+ ]+ },+ {+ "description": "3-of-5, 128 bits, not extendable",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 3,+ 5+ ]+ ]+ },+ "identifier": 42,+ "extendable": false,+ "iterationExponent": 1,+ "passphrase": "TREZOR",+ "masterSecret": "4142434445464748494a4b4c4d4e4f50",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [+ "58fb87176761c432c8410001e0454f62"+ ],+ "digest": "7235aa0b577e0ffceb3978f5"+ }+ ],+ "mnemonics": [+ [+ "acid extra academic acne blue wine buyer install steady scandal silent lilac easy adapt lecture fiction step nylon indicate umbrella",+ "acid extra academic agree body market fraction duckling laden often freshman temple scene year brother image vexed science disaster explain",+ "acid extra academic amazing already repeat husband escape evaluate metric equation crunch robin slow episode strategy grant diagnose rhythm minister",+ "acid extra academic arcade alto slice dining adorn union smear syndrome episode downtown declare sniff wits entrance hairy various debut",+ "acid extra academic axle diploma verdict marathon shrimp educate traveler random maximum corner crush crazy fatal together campus bedroom frost"+ ]+ ]+ },+ {+ "description": "3-of-5, 256 bits, exponent 2",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 3,+ 5+ ]+ ]+ },+ "identifier": 32767,+ "extendable": true,+ "iterationExponent": 2,+ "passphrase": "pass phrase",+ "masterSecret": "4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [+ "1e83c5fa3334312d90c0f1dfffa3b191d8e38dbf7ccf5e3d0c974a88ce700563"+ ],+ "digest": "ba2957e47955e605c968472eb58339f2e2fd28917f72760ff7c88606"+ }+ ],+ "mnemonics": [+ [+ "zero wits academic acne adapt pants scramble trash depend aquatic curly element average island yoga medal broken inside theater diet voting deploy frozen veteran sharp source lying dive acid float voice slap unhappy",+ "zero wits academic agree airline adapt cards royal duckling universe friar fatal cylinder become admit loan vegan boring cards escape family task amazing work temple loyalty vanish western modify herald hour tricycle salon",+ "zero wits academic amazing antenna stay genre hospital stilt ruler juice facility story industry formal clothes flexible blimp painting describe tofu fishing critical beyond helpful remove march humidity heat muscle mailman likely kind",+ "zero wits academic arcade average jerky repeat endorse spew class answer echo warmth behavior pile dance quiet injury friendly entrance element repeat ladle bulge email type visual presence zero payment bike percent extra",+ "zero wits academic axle amazing ultimate python switch patrol hobo genre smith depict national phantom fatal clinic cowboy sunlight research exotic brother work depict acquire legs holy plunge random maiden realize walnut domestic"+ ]+ ]+ },+ {+ "description": "2-of-2, 160 bits",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 2,+ 2+ ]+ ]+ },+ "identifier": 0,+ "extendable": true,+ "iterationExponent": 0,+ "passphrase": "",+ "masterSecret": "4142434445464748494a4b4c4d4e4f5051525354",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [],+ "digest": "c6058760ba014cea8b09057071391884"+ }+ ],+ "mnemonics": [+ [+ "academic again academic acid beyond focus device elevator alto clinic industry legal both relate identify story scroll emerald mortgage priest permit browser party",+ "academic again academic agency mailman usual enlarge airport living airport dream endorse laden usual arcade canyon being drove finger surprise hazard being exchange"+ ]+ ]+ },+ {+ "description": "2 of 3 groups: 1-of-1, 2-of-3, 3-of-5, 128 bits",+ "scheme": {+ "groupThreshold": 2,+ "groups": [+ [+ 1,+ 1+ ],+ [+ 2,+ 3+ ],+ [+ 3,+ 5+ ]+ ]+ },+ "identifier": 12345,+ "extendable": true,+ "iterationExponent": 0,+ "passphrase": "TREZOR",+ "masterSecret": "4142434445464748494a4b4c4d4e4f50",+ "random": [+ {+ "shares": [],+ "digest": "20520badd1c2f4add42f6f8b"+ },+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [],+ "digest": "4b5df7a7faee6e3110e7dd40"+ },+ {+ "shares": [+ "307499552909a562c0c8da3c50734567"+ ],+ "digest": "ab0c66efb073adb6bb838ea1"+ }+ ],+ "mnemonics": [+ [+ "garlic single acrobat leader anatomy fridge album sympathy paper mandate vegan secret adult identify python diagnose garlic idle tidy fridge"+ ],+ [+ "garlic single beard leaf champion news smirk extend mule grownup orange dismiss paces believe teacher learn crowd hazard rapids believe",+ "garlic single beard lily away emphasis hamster display legend square airport priority ceiling cradle smirk royal desktop junction enjoy jewelry",+ "garlic single beard lungs aircraft domestic lobe chubby jerky capital prisoner beam physics grant replace prospect blanket diminish minister spine"+ ],+ [+ "garlic single ceramic learn apart infant gray famous elder permit mama arcade dictate dream either desert forbid fumes hobo texture",+ "garlic single ceramic lips easel cluster focus viral phantom sharp darkness company blue armed phantom market epidemic grin problem execute",+ "garlic single ceramic luxury antenna example manager trial pencil geology pleasure wine transfer satisfy payment oven ruin ordinary smirk lunch",+ "garlic single ceramic march discuss cards recall exhaust element aspect auction snapshot senior vanish enemy coal soldier petition apart climate",+ "garlic single ceramic method blimp elegant lend minister warn exact guest mixture prayer ocean chest breathe alarm civil painting seafood"+ ]+ ]+ },+ {+ "description": "1 of 2 groups: 2-of-3 and 2-of-3, 256 bits, not extendable",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 2,+ 3+ ],+ [+ 2,+ 3+ ]+ ]+ },+ "identifier": 1,+ "extendable": false,+ "iterationExponent": 1,+ "passphrase": "",+ "masterSecret": "4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [],+ "digest": "990df1d6f4a02cca605b9418b260b7c70dfad975780be420c2bc4cdf"+ },+ {+ "shares": [],+ "digest": "8588cc4bd3f26057b5095f8f1462aef1a6421db7917bfa1ad21164f9"+ }+ ],+ "mnemonics": [+ [+ "academic always academic echo alive river tolerate elite quiet nail diet unusual shaped arena quantity discuss rescue laden sympathy detect romp decision glance duration deal unusual pupal fishing universe national acid endorse smoking",+ "academic always academic email anxiety decision ladybug spit piece gesture traffic grant humidity downtown criminal calcium execute acne veteran junction ending dish sprinkle traffic elite gray legend example scout grant credit glen story",+ "academic always academic entrance aviation glasses slow hazard crisis domestic midst military describe client obesity camera fake ruler member saver keyboard chest beard wireless guard welcome spend angel browser voter brother texture crystal"+ ],+ [+ "academic always away echo admit race airline lunar picture freshman priority manager vexed tricycle cylinder violence coastal order dish mandate declare course hanger saver smug squeeze finance various training hormone dining security brother",+ "academic always away email anatomy raisin should reward jury timely unfold orbit inside welcome glad nylon hazard anatomy benefit pajamas sympathy smear bucket carbon desire fraction friar plunge become mortgage desert flexible adult",+ "academic always away entrance airport intend leaf voting rich profile critical smith ecology short rapids elegant cards industry hormone race equation genre magazine judicial browser single enforce darkness ancestor camera elite brother arena"+ ]+ ]+ },+ {+ "description": "4-of-4, 128 bits: two random shares",+ "scheme": {+ "groupThreshold": 1,+ "groups": [+ [+ 4,+ 4+ ]+ ]+ },+ "identifier": 100,+ "extendable": true,+ "iterationExponent": 0,+ "passphrase": "",+ "masterSecret": "30313233343536373839616263646566",+ "random": [+ {+ "shares": [],+ "digest": ""+ },+ {+ "shares": [+ "e34d5d16e5543909b2dd338dd0b8fc11",+ "60ddb24c0d605a48fcc89a42f4626e08"+ ],+ "digest": "2536c03e1df9d5b3b4cbe3ad"+ }+ ],+ "mnemonics": [+ [+ "acquire clay academic acquire dish exchange industry hormone filter dismiss coal custody example math elegant mental agency fitness jerky pumps",+ "acquire clay academic aide branch symbolic shaped royal flea forecast class skin cluster elegant software coal lend curly math glasses",+ "acquire clay academic ambition duke wrap thumb soul sack bracelet tofu seafood ranked beyond blanket loud emperor listen kind distance",+ "acquire clay academic arena depend iris process surface mild photo veteran mailman impulse display force bulge obtain campus downtown exchange"+ ]+ ]+ }+]
+ data/slip39_vectors.json view
@@ -0,0 +1,406 @@+[+ [+ "1. Valid mnemonic without sharing (128 bits)",+ [+ "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision keyboard"+ ],+ "bb54aac4b89dc868ba37d9cc21b2cece",+ "xprv9s21ZrQH143K4QViKpwKCpS2zVbz8GrZgpEchMDg6KME9HZtjfL7iThE9w5muQA4YPHKN1u5VM1w8D4pvnjxa2BmpGMfXr7hnRrRHZ93awZ"+ ],+ [+ "2. Mnemonic with invalid checksum (128 bits)",+ [+ "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision kidney"+ ],+ "",+ ""+ ],+ [+ "3. Mnemonic with invalid padding (128 bits)",+ [+ "duckling enlarge academic academic email result length solution fridge kidney coal piece deal husband erode duke ajar music cargo fitness"+ ],+ "",+ ""+ ],+ [+ "4. Basic sharing 2-of-3 (128 bits)",+ [+ "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed",+ "shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking"+ ],+ "b43ceb7e57a0ea8766221624d01b0864",+ "xprv9s21ZrQH143K2nNuAbfWPHBtfiSCS14XQgb3otW4pX655q58EEZeC8zmjEUwucBu9dPnxdpbZLCn57yx45RBkwJHnwHFjZK4XPJ8SyeYjYg"+ ],+ [+ "5. Basic sharing 2-of-3 (128 bits)",+ [+ "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed"+ ],+ "",+ ""+ ],+ [+ "6. Mnemonics with different identifiers (128 bits)",+ [+ "adequate smoking academic acid debut wine petition glen cluster slow rhyme slow simple epidemic rumor junk tracks treat olympic tolerate",+ "adequate stay academic agency agency formal party ting frequent learn upstairs remember smear leaf damage anatomy ladle market hush corner"+ ],+ "",+ ""+ ],+ [+ "7. Mnemonics with different iteration exponents (128 bits)",+ [+ "peasant leaves academic acid desert exact olympic math alive axle trial tackle drug deny decent smear dominant desert bucket remind",+ "peasant leader academic agency cultural blessing percent network envelope medal junk primary human pumps jacket fragment payroll ticket evoke voice"+ ],+ "",+ ""+ ],+ [+ "8. Mnemonics with mismatching group thresholds (128 bits)",+ [+ "liberty category beard echo animal fawn temple briefing math username various wolf aviation fancy visual holy thunder yelp helpful payment",+ "liberty category beard email beyond should fancy romp founder easel pink holy hairy romp loyalty material victim owner toxic custody",+ "liberty category academic easy being hazard crush diminish oral lizard reaction cluster force dilemma deploy force club veteran expect photo"+ ],+ "",+ ""+ ],+ [+ "9. Mnemonics with mismatching group counts (128 bits)",+ [+ "average senior academic leaf broken teacher expect surface hour capture obesity desire negative dynamic dominant pistol mineral mailman iris aide",+ "average senior academic agency curious pants blimp spew clothes slice script dress wrap firm shaft regular slavery negative theater roster"+ ],+ "",+ ""+ ],+ [+ "10. Mnemonics with greater group threshold than group counts (128 bits)",+ [+ "music husband acrobat acid artist finance center either graduate swimming object bike medical clothes station aspect spider maiden bulb welcome",+ "music husband acrobat agency advance hunting bike corner density careful material civil evil tactics remind hawk discuss hobo voice rainbow",+ "music husband beard academic black tricycle clock mayor estimate level photo episode exclude ecology papa source amazing salt verify divorce"+ ],+ "",+ ""+ ],+ [+ "11. Mnemonics with duplicate member indices (128 bits)",+ [+ "device stay academic always dive coal antenna adult black exceed stadium herald advance soldier busy dryer daughter evaluate minister laser",+ "device stay academic always dwarf afraid robin gravity crunch adjust soul branch walnut coastal dream costume scholar mortgage mountain pumps"+ ],+ "",+ ""+ ],+ [+ "12. Mnemonics with mismatching member thresholds (128 bits)",+ [+ "hour painting academic academic device formal evoke guitar random modern justice filter withdraw trouble identify mailman insect general cover oven",+ "hour painting academic agency artist again daisy capital beaver fiber much enjoy suitable symbolic identify photo editor romp float echo"+ ],+ "",+ ""+ ],+ [+ "13. Mnemonics giving an invalid digest (128 bits)",+ [+ "guilt walnut academic acid deliver remove equip listen vampire tactics nylon rhythm failure husband fatigue alive blind enemy teaspoon rebound",+ "guilt walnut academic agency brave hamster hobo declare herd taste alpha slim criminal mild arcade formal romp branch pink ambition"+ ],+ "",+ ""+ ],+ [+ "14. Insufficient number of groups (128 bits, case 1)",+ [+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice"+ ],+ "",+ ""+ ],+ [+ "15. Insufficient number of groups (128 bits, case 2)",+ [+ "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join",+ "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter"+ ],+ "",+ ""+ ],+ [+ "16. Threshold number of groups, but insufficient number of members in one group (128 bits)",+ [+ "eraser senior decision shadow artist work morning estate greatest pipeline plan ting petition forget hormone flexible general goat admit surface",+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice"+ ],+ "",+ ""+ ],+ [+ "17. Threshold number of groups and members in each group (128 bits, case 1)",+ [+ "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter",+ "eraser senior ceramic snake clay various huge numb argue hesitate auction category timber browser greatest hanger petition script leaf pickup",+ "eraser senior ceramic shaft dynamic become junior wrist silver peasant force math alto coal amazing segment yelp velvet image paces",+ "eraser senior ceramic round column hawk trust auction smug shame alive greatest sheriff living perfect corner chest sled fumes adequate",+ "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing"+ ],+ "7c3397a292a5941682d7a4ae2d898d11",+ "xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV"+ ],+ [+ "18. Threshold number of groups and members in each group (128 bits, case 2)",+ [+ "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing",+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice",+ "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join"+ ],+ "7c3397a292a5941682d7a4ae2d898d11",+ "xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV"+ ],+ [+ "19. Threshold number of groups and members in each group (128 bits, case 3)",+ [+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice",+ "eraser senior acrobat romp bishop medical gesture pumps secret alive ultimate quarter priest subject class dictate spew material endless market"+ ],+ "7c3397a292a5941682d7a4ae2d898d11",+ "xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV"+ ],+ [+ "20. Valid mnemonic without sharing (256 bits)",+ [+ "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect luck"+ ],+ "989baf9dcaad5b10ca33dfd8cc75e42477025dce88ae83e75a230086a0e00e92",+ "xprv9s21ZrQH143K41mrxxMT2FpiheQ9MFNmWVK4tvX2s28KLZAhuXWskJCKVRQprq9TnjzzzEYePpt764csiCxTt22xwGPiRmUjYUUdjaut8RM"+ ],+ [+ "21. Mnemonic with invalid checksum (256 bits)",+ [+ "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect lunar"+ ],+ "",+ ""+ ],+ [+ "22. Mnemonic with invalid padding (256 bits)",+ [+ "theory painting academic academic campus sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips facility obtain sister"+ ],+ "",+ ""+ ],+ [+ "23. Basic sharing 2-of-3 (256 bits)",+ [+ "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap",+ "humidity disease academic agency actress jacket gross physics cylinder solution fake mortgage benefit public busy prepare sharp friar change work slow purchase ruler again tricycle involve viral wireless mixture anatomy desert cargo upgrade"+ ],+ "c938b319067687e990e05e0da0ecce1278f75ff58d9853f19dcaeed5de104aae",+ "xprv9s21ZrQH143K3a4GRMgK8WnawupkwkP6gyHxRsXnMsYPTPH21fWwNcAytijtfyftqNfiaY8LgQVdBQvHZ9FBvtwdjC7LCYxjYruJFuLzyMQ"+ ],+ [+ "24. Basic sharing 2-of-3 (256 bits)",+ [+ "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap"+ ],+ "",+ ""+ ],+ [+ "25. Mnemonics with different identifiers (256 bits)",+ [+ "smear husband academic acid deadline scene venture distance dive overall parking bracelet elevator justice echo burning oven chest duke nylon",+ "smear isolate academic agency alpha mandate decorate burden recover guard exercise fatal force syndrome fumes thank guest drift dramatic mule"+ ],+ "",+ ""+ ],+ [+ "26. Mnemonics with different iteration exponents (256 bits)",+ [+ "finger trash academic acid average priority dish revenue academic hospital spirit western ocean fact calcium syndrome greatest plan losing dictate",+ "finger traffic academic agency building lilac deny paces subject threaten diploma eclipse window unknown health slim piece dragon focus smirk"+ ],+ "",+ ""+ ],+ [+ "27. Mnemonics with mismatching group thresholds (256 bits)",+ [+ "flavor pink beard echo depart forbid retreat become frost helpful juice unwrap reunion credit math burning spine black capital lair",+ "flavor pink beard email diet teaspoon freshman identify document rebound cricket prune headset loyalty smell emission skin often square rebound",+ "flavor pink academic easy credit cage raisin crazy closet lobe mobile become drink human tactics valuable hand capture sympathy finger"+ ],+ "",+ ""+ ],+ [+ "28. Mnemonics with mismatching group counts (256 bits)",+ [+ "column flea academic leaf debut extra surface slow timber husky lawsuit game behavior husky swimming already paper episode tricycle scroll",+ "column flea academic agency blessing garbage party software stadium verify silent umbrella therapy decorate chemical erode dramatic eclipse replace apart"+ ],+ "",+ ""+ ],+ [+ "29. Mnemonics with greater group threshold than group counts (256 bits)",+ [+ "smirk pink acrobat acid auction wireless impulse spine sprinkle fortune clogs elbow guest hush loyalty crush dictate tracks airport talent",+ "smirk pink acrobat agency dwarf emperor ajar organize legs slice harvest plastic dynamic style mobile float bulb health coding credit",+ "smirk pink beard academic alto strategy carve shame language rapids ruin smart location spray training acquire eraser endorse submit peaceful"+ ],+ "",+ ""+ ],+ [+ "30. Mnemonics with duplicate member indices (256 bits)",+ [+ "fishing recover academic always device craft trend snapshot gums skin downtown watch device sniff hour clock public maximum garlic born",+ "fishing recover academic always aircraft view software cradle fangs amazing package plastic evaluate intend penalty epidemic anatomy quarter cage apart"+ ],+ "",+ ""+ ],+ [+ "31. Mnemonics with mismatching member thresholds (256 bits)",+ [+ "evoke garden academic academic answer wolf scandal modern warmth station devote emerald market physics surface formal amazing aquatic gesture medical",+ "evoke garden academic agency deal revenue knit reunion decrease magazine flexible company goat repair alarm military facility clogs aide mandate"+ ],+ "",+ ""+ ],+ [+ "32. Mnemonics giving an invalid digest (256 bits)",+ [+ "river deal academic acid average forbid pistol peanut custody bike class aunt hairy merit valid flexible learn ajar very easel",+ "river deal academic agency camera amuse lungs numb isolate display smear piece traffic worthy year patrol crush fact fancy emission"+ ],+ "",+ ""+ ],+ [+ "33. Insufficient number of groups (256 bits, case 1)",+ [+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium"+ ],+ "",+ ""+ ],+ [+ "34. Insufficient number of groups (256 bits, case 2)",+ [+ "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",+ "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install"+ ],+ "",+ ""+ ],+ [+ "35. Threshold number of groups, but insufficient number of members in one group (256 bits)",+ [+ "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club",+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium"+ ],+ "",+ ""+ ],+ [+ "36. Threshold number of groups and members in each group (256 bits, case 1)",+ [+ "wildlife deal ceramic round aluminum pitch goat racism employer miracle percent math decision episode dramatic editor lily prospect program scene rebuild display sympathy have single mustang junction relate often chemical society wits estate",+ "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",+ "wildlife deal ceramic scatter argue equip vampire together ruin reject literary rival distance aquatic agency teammate rebound false argue miracle stay again blessing peaceful unknown cover beard acid island language debris industry idle",+ "wildlife deal ceramic snake agree voter main lecture axis kitchen physics arcade velvet spine idea scroll promise platform firm sharp patrol divorce ancestor fantasy forbid goat ajar believe swimming cowboy symbolic plastic spelling",+ "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club"+ ],+ "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b",+ "xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c"+ ],+ [+ "37. Threshold number of groups and members in each group (256 bits, case 2)",+ [+ "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium",+ "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install"+ ],+ "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b",+ "xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c"+ ],+ [+ "38. Threshold number of groups and members in each group (256 bits, case 3)",+ [+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium",+ "wildlife deal acrobat romp anxiety axis starting require metric flexible geology game drove editor edge screw helpful have huge holy making pitch unknown carve holiday numb glasses survive already tenant adapt goat fangs"+ ],+ "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b",+ "xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c"+ ],+ [+ "39. Mnemonic with insufficient length",+ [+ "junk necklace academic academic acne isolate join hesitate lunar roster dough calcium chemical ladybug amount mobile glasses verify cylinder"+ ],+ "",+ ""+ ],+ [+ "40. Mnemonic with invalid master secret length",+ [+ "fraction necklace academic academic award teammate mouse regular testify coding building member verdict purchase blind camera duration email prepare spirit quarter"+ ],+ "",+ ""+ ],+ [+ "41. Valid mnemonics which can detect some errors in modular arithmetic",+ [+ "herald flea academic cage avoid space trend estate dryer hairy evoke eyebrow improve airline artwork garlic premium duration prevent oven",+ "herald flea academic client blue skunk class goat luxury deny presence impulse graduate clay join blanket bulge survive dish necklace",+ "herald flea academic acne advance fused brother frozen broken game ranked ajar already believe check install theory angry exercise adult"+ ],+ "ad6f2ad8b59bbbaa01369b9006208d9a",+ "xprv9s21ZrQH143K2R4HJxcG1eUsudvHM753BZ9vaGkpYCoeEhCQx147C5qEcupPHxcXYfdYMwJmsKXrHDhtEwutxTTvFzdDCZVQwHneeQH8ioH"+ ],+ [+ "42. Valid extendable mnemonic without sharing (128 bits)",+ [+ "testify swimming academic academic column loyalty smear include exotic bedroom exotic wrist lobe cover grief golden smart junior estimate learn"+ ],+ "1679b4516e0ee5954351d288a838f45e",+ "xprv9s21ZrQH143K2w6eTpQnB73CU8Qrhg6gN3D66Jr16n5uorwoV7CwxQ5DofRPyok5DyRg4Q3BfHfCgJFk3boNRPPt1vEW1ENj2QckzVLQFXu"+ ],+ [+ "43. Extendable basic sharing 2-of-3 (128 bits)",+ [+ "enemy favorite academic acid cowboy phrase havoc level response walnut budget painting inside trash adjust froth kitchen learn tidy punish",+ "enemy favorite academic always academic sniff script carpet romp kind promise scatter center unfair training emphasis evening belong fake enforce"+ ],+ "48b1a4b80b8c209ad42c33672bdaa428",+ "xprv9s21ZrQH143K4FS1qQdXYAFVAHiSAnjj21YAKGh2CqUPJ2yQhMmYGT4e5a2tyGLiVsRgTEvajXkxhg92zJ8zmWZas9LguQWz7WZShfJg6RS"+ ],+ [+ "44. Valid extendable mnemonic without sharing (256 bits)",+ [+ "impulse calcium academic academic alcohol sugar lyrics pajamas column facility finance tension extend space birthday rainbow swimming purple syndrome facility trial warn duration snapshot shadow hormone rhyme public spine counter easy hawk album"+ ],+ "8340611602fe91af634a5f4608377b5235fa2d757c51d720c0c7656249a3035f",+ "xprv9s21ZrQH143K2yJ7S8bXMiGqp1fySH8RLeFQKQmqfmmLTRwWmAYkpUcWz6M42oGoFMJRENmvsGQmunWTdizsi8v8fku8gpbVvYSiCYJTF1Y"+ ],+ [+ "45. Extendable basic sharing 2-of-3 (256 bits)",+ [+ "western apart academic always artist resident briefing sugar woman oven coding club ajar merit pecan answer prisoner artist fraction amount desktop mild false necklace muscle photo wealthy alpha category unwrap spew losing making",+ "western apart academic acid answer ancient auction flip image penalty oasis beaver multiple thunder problem switch alive heat inherit superior teaspoon explain blanket pencil numb lend punish endless aunt garlic humidity kidney observe"+ ],+ "8dc652d6d6cd370d8c963141f6d79ba440300f25c467302c1d966bff8f62300d",+ "xprv9s21ZrQH143K2eFW2zmu3aayWWd6MJZBG7RebW35fiKcoCZ6jFi6U5gzffB9McDdiKTecUtRqJH9GzueCXiQK1LaQXdgthS8DgWfC8Uu3z7"+ ]+]
+ data/slip39_wordlist.txt view
@@ -0,0 +1,1024 @@+academic+acid+acne+acquire+acrobat+activity+actress+adapt+adequate+adjust+admit+adorn+adult+advance+advocate+afraid+again+agency+agree+aide+aircraft+airline+airport+ajar+alarm+album+alcohol+alien+alive+alpha+already+alto+aluminum+always+amazing+ambition+amount+amuse+analysis+anatomy+ancestor+ancient+angel+angry+animal+answer+antenna+anxiety+apart+aquatic+arcade+arena+argue+armed+artist+artwork+aspect+auction+august+aunt+average+aviation+avoid+award+away+axis+axle+beam+beard+beaver+become+bedroom+behavior+being+believe+belong+benefit+best+beyond+bike+biology+birthday+bishop+black+blanket+blessing+blimp+blind+blue+body+bolt+boring+born+both+boundary+bracelet+branch+brave+breathe+briefing+broken+brother+browser+bucket+budget+building+bulb+bulge+bumpy+bundle+burden+burning+busy+buyer+cage+calcium+camera+campus+canyon+capacity+capital+capture+carbon+cards+careful+cargo+carpet+carve+category+cause+ceiling+center+ceramic+champion+change+charity+check+chemical+chest+chew+chubby+cinema+civil+class+clay+cleanup+client+climate+clinic+clock+clogs+closet+clothes+club+cluster+coal+coastal+coding+column+company+corner+costume+counter+course+cover+cowboy+cradle+craft+crazy+credit+cricket+criminal+crisis+critical+crowd+crucial+crunch+crush+crystal+cubic+cultural+curious+curly+custody+cylinder+daisy+damage+dance+darkness+database+daughter+deadline+deal+debris+debut+decent+decision+declare+decorate+decrease+deliver+demand+density+deny+depart+depend+depict+deploy+describe+desert+desire+desktop+destroy+detailed+detect+device+devote+diagnose+dictate+diet+dilemma+diminish+dining+diploma+disaster+discuss+disease+dish+dismiss+display+distance+dive+divorce+document+domain+domestic+dominant+dough+downtown+dragon+dramatic+dream+dress+drift+drink+drove+drug+dryer+duckling+duke+duration+dwarf+dynamic+early+earth+easel+easy+echo+eclipse+ecology+edge+editor+educate+either+elbow+elder+election+elegant+element+elephant+elevator+elite+else+email+emerald+emission+emperor+emphasis+employer+empty+ending+endless+endorse+enemy+energy+enforce+engage+enjoy+enlarge+entrance+envelope+envy+epidemic+episode+equation+equip+eraser+erode+escape+estate+estimate+evaluate+evening+evidence+evil+evoke+exact+example+exceed+exchange+exclude+excuse+execute+exercise+exhaust+exotic+expand+expect+explain+express+extend+extra+eyebrow+facility+fact+failure+faint+fake+false+family+famous+fancy+fangs+fantasy+fatal+fatigue+favorite+fawn+fiber+fiction+filter+finance+findings+finger+firefly+firm+fiscal+fishing+fitness+flame+flash+flavor+flea+flexible+flip+float+floral+fluff+focus+forbid+force+forecast+forget+formal+fortune+forward+founder+fraction+fragment+frequent+freshman+friar+fridge+friendly+frost+froth+frozen+fumes+funding+furl+fused+galaxy+game+garbage+garden+garlic+gasoline+gather+general+genius+genre+genuine+geology+gesture+glad+glance+glasses+glen+glimpse+goat+golden+graduate+grant+grasp+gravity+gray+greatest+grief+grill+grin+grocery+gross+group+grownup+grumpy+guard+guest+guilt+guitar+gums+hairy+hamster+hand+hanger+harvest+have+havoc+hawk+hazard+headset+health+hearing+heat+helpful+herald+herd+hesitate+hobo+holiday+holy+home+hormone+hospital+hour+huge+human+humidity+hunting+husband+hush+husky+hybrid+idea+identify+idle+image+impact+imply+improve+impulse+include+income+increase+index+indicate+industry+infant+inform+inherit+injury+inmate+insect+inside+install+intend+intimate+invasion+involve+iris+island+isolate+item+ivory+jacket+jerky+jewelry+join+judicial+juice+jump+junction+junior+junk+jury+justice+kernel+keyboard+kidney+kind+kitchen+knife+knit+laden+ladle+ladybug+lair+lamp+language+large+laser+laundry+lawsuit+leader+leaf+learn+leaves+lecture+legal+legend+legs+lend+length+level+liberty+library+license+lift+likely+lilac+lily+lips+liquid+listen+literary+living+lizard+loan+lobe+location+losing+loud+loyalty+luck+lunar+lunch+lungs+luxury+lying+lyrics+machine+magazine+maiden+mailman+main+makeup+making+mama+manager+mandate+mansion+manual+marathon+march+market+marvel+mason+material+math+maximum+mayor+meaning+medal+medical+member+memory+mental+merchant+merit+method+metric+midst+mild+military+mineral+minister+miracle+mixed+mixture+mobile+modern+modify+moisture+moment+morning+mortgage+mother+mountain+mouse+move+much+mule+multiple+muscle+museum+music+mustang+nail+national+necklace+negative+nervous+network+news+nuclear+numb+numerous+nylon+oasis+obesity+object+observe+obtain+ocean+often+olympic+omit+oral+orange+orbit+order+ordinary+organize+ounce+oven+overall+owner+paces+pacific+package+paid+painting+pajamas+pancake+pants+papa+paper+parcel+parking+party+patent+patrol+payment+payroll+peaceful+peanut+peasant+pecan+penalty+pencil+percent+perfect+permit+petition+phantom+pharmacy+photo+phrase+physics+pickup+picture+piece+pile+pink+pipeline+pistol+pitch+plains+plan+plastic+platform+playoff+pleasure+plot+plunge+practice+prayer+preach+predator+pregnant+premium+prepare+presence+prevent+priest+primary+priority+prisoner+privacy+prize+problem+process+profile+program+promise+prospect+provide+prune+public+pulse+pumps+punish+puny+pupal+purchase+purple+python+quantity+quarter+quick+quiet+race+racism+radar+railroad+rainbow+raisin+random+ranked+rapids+raspy+reaction+realize+rebound+rebuild+recall+receiver+recover+regret+regular+reject+relate+remember+remind+remove+render+repair+repeat+replace+require+rescue+research+resident+response+result+retailer+retreat+reunion+revenue+review+reward+rhyme+rhythm+rich+rival+river+robin+rocky+romantic+romp+roster+round+royal+ruin+ruler+rumor+sack+safari+salary+salon+salt+satisfy+satoshi+saver+says+scandal+scared+scatter+scene+scholar+science+scout+scramble+screw+script+scroll+seafood+season+secret+security+segment+senior+shadow+shaft+shame+shaped+sharp+shelter+sheriff+short+should+shrimp+sidewalk+silent+silver+similar+simple+single+sister+skin+skunk+slap+slavery+sled+slice+slim+slow+slush+smart+smear+smell+smirk+smith+smoking+smug+snake+snapshot+sniff+society+software+soldier+solution+soul+source+space+spark+speak+species+spelling+spend+spew+spider+spill+spine+spirit+spit+spray+sprinkle+square+squeeze+stadium+staff+standard+starting+station+stay+steady+step+stick+stilt+story+strategy+strike+style+subject+submit+sugar+suitable+sunlight+superior+surface+surprise+survive+sweater+swimming+swing+switch+symbolic+sympathy+syndrome+system+tackle+tactics+tadpole+talent+task+taste+taught+taxi+teacher+teammate+teaspoon+temple+tenant+tendency+tension+terminal+testify+texture+thank+that+theater+theory+therapy+thorn+threaten+thumb+thunder+ticket+tidy+timber+timely+ting+tofu+together+tolerate+total+toxic+tracks+traffic+training+transfer+trash+traveler+treat+trend+trial+tricycle+trip+triumph+trouble+true+trust+twice+twin+type+typical+ugly+ultimate+umbrella+uncover+undergo+unfair+unfold+unhappy+union+universe+unkind+unknown+unusual+unwrap+upgrade+upstairs+username+usher+usual+valid+valuable+vampire+vanish+various+vegan+velvet+venture+verdict+verify+very+veteran+vexed+victim+video+view+vintage+violence+viral+visitor+visual+vitamins+vocal+voice+volume+voter+voting+walnut+warmth+warn+watch+wavy+wealthy+weapon+webcam+welcome+welfare+western+width+wildlife+window+wine+wireless+wisdom+withdraw+wits+wolf+woman+work+worthy+wrap+wrist+writing+wrote+year+yelp+yield+yoga+zero
+ data/taproot_mainnet_spends.json view
@@ -0,0 +1,295 @@+{+ "description": "Taproot script path spends from Bitcoin mainnet blocks 840000 and 900000. Each entry is one input: the transaction id, the input index, the height of the block that holds the transaction, the output script of the coin it spends, and its witness items. The scripts are 34 to 635 bytes long, so their lengths cross the CompactSize boundaries at 128 (where a base-128 varint differs) and at 253.",+ "source": "Witnesses from the raw blocks 0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5 (840000) and 000000000000000000010538edbfd2d5b809a33dd83f284aeea41c6d0d96968a (900000). Spent output scripts from the transaction API of blockstream.info, fetched on 2026-11-05.",+ "spends": [+ {+ "txid": "23913fd423afba15d42f85c38bf505608c10401afeb7264aec4925dfa53be807",+ "input": 0,+ "height": 840000,+ "scriptBytes": 57,+ "spentScript": "512060e41c6e17335a21dd76fc3127c4afdd9a7e006972cb3b2b3123b5b54cc7db36",+ "witness": [+ "f9884962f3fa775498074471f79718355434e516d84f28832021c0c5ea71498c35a2b27a25d7a9e3ac4f6158ee9bc740ee3da5c7cd322805befac822c062aab6",+ "046fff956c6627148a4d59222e77d151e44ed82ad6ac8323314cffb7f116846a",+ "a91407c7a31cb91bc40a3c6a49d6ce76ae7849d27dc08820cb1fdeffba50230a4afde2863a48b4e924758e3730065c944c59bda01492f88aac",+ "c055e3697e2c977fa80cd04be6abdd9c8238c8abd9428f5565bc72bf2b53325bf26ac19662386e822838c99160225a0a01c6b0eccc2034080038a614f52c1d7034"+ ]+ },+ {+ "txid": "05f8584cf4dbe34ef677f8f316fcac9e6e4ccb0e298d53fd21edaac7787660ee",+ "input": 0,+ "height": 840000,+ "scriptBytes": 34,+ "spentScript": "512003642f6114a2e45208c4f245abb5b6aaa9edbb6dce55454cd30ab9ab56a9a066",+ "witness": [+ "06f31eccce157d7aa4788d7b9238673ec777ca7b658a571532c9a99a4159a48bc0ac85ca32ca69ad87690d17acd74bb1b6cf490bce9fb552c5c0acd62e6c352b",+ "0063036f7264510a746578742f706c61696e000d3834303030302e6269746d617068",+ "c1c0918ac338897c6f8f3b723c05c629dff665a1d75a51da24bb79ea0f4bf597e5"+ ]+ },+ {+ "txid": "0984c007f1b479fd924a548821f3784d1bc13422138813ed2f27ad7e289a91f1",+ "input": 0,+ "height": 840000,+ "scriptBytes": 34,+ "spentScript": "5120fa6d72387476873a4e0b8f231357b9f41799e0d069e5b4358e36f7225baab849",+ "witness": [+ "e9808f80331b2d90cec7a6b38623720178b1d2131cb49af2a0950110dad87a8fd8f60d9f9cd5f7abda4501cd33187a712f3d15fe52802fc84199cce19a3394f6",+ "0063036f7264510a746578742f706c61696e000d3833393939352e6269746d617068",+ "c0c72952c171396617fbb3a4e0dbb8f4e92eb5835738b3ba0a4c6f0c11ef0c920b"+ ]+ },+ {+ "txid": "0a10edc679a549b4356944c829ffc6b32f058648c068b14d3cc977b6b2e27fee",+ "input": 0,+ "height": 840000,+ "scriptBytes": 34,+ "spentScript": "512003642cfc8d45ee49ead7ea6c975ede46260e9c4bb4063dfc0cb9e66f72bff0ab",+ "witness": [+ "fe51921f67ac2f30f09787f57bee60a82c927bf6b1e775ab025dfa295265ce985a3cd576b020e833faafee2f54aba126519b07bde0a518404731986b6128f857",+ "0063036f7264510a746578742f706c61696e000d3834303030302e6269746d617068",+ "c1c72952c171396617fbb3a4e0dbb8f4e92eb5835738b3ba0a4c6f0c11ef0c920b"+ ]+ },+ {+ "txid": "101cb8945486ec07bbeb42ce266f70e10307d564fd4ecff95fd10b223cb53fc7",+ "input": 0,+ "height": 840000,+ "scriptBytes": 34,+ "spentScript": "512055533cb5225d5799eea47d606d65bd399e37af39584ac54447b9ae35565da769",+ "witness": [+ "ddcea277c64630898efacfa97b0a37777f74e555e2fd21e312e943b6386161ca3a575f6141d8811a2aaf9091bccfb2b5b9c79721c6e61928a6e4161653218d79",+ "0063036f7264510a746578742f706c61696e000d3833393939392e6269746d617068",+ "c0c72952c171396617fbb3a4e0dbb8f4e92eb5835738b3ba0a4c6f0c11ef0c920b"+ ]+ },+ {+ "txid": "15c735a453523784efd266c7fa4e39d927a15049ab2071836c83be7b4765896f",+ "input": 0,+ "height": 840000,+ "scriptBytes": 34,+ "spentScript": "51202683b1d252fc867a720e7273e956b533fc20001af36bcb402dce94e12604aeaa",+ "witness": [+ "51f3e8b6e04e5526cb94a9578e31adcf0f0b723162e6b923c9fed3abf84117c3a08e0036a644f7e292640ee1f87a10916bcc595b8c10606b6c41e45b81f0c305",+ "0063036f7264510a746578742f706c61696e000d3833393939342e6269746d617068",+ "c1c72952c171396617fbb3a4e0dbb8f4e92eb5835738b3ba0a4c6f0c11ef0c920b"+ ]+ },+ {+ "txid": "4f4d8766cf284dd9c7cd18a0dde96c110106ce7ea426b9c09b87621e1489f150",+ "input": 0,+ "height": 900000,+ "scriptBytes": 128,+ "spentScript": "51201bc5020f194da1886b5c8a4f805b612840dcc9136dfcaee05111c2fe17d8f7c7",+ "witness": [+ "b828878ff7444245a41d7500940085a449832ca1b8637bf844488eb98549ff9a43406a9f416331c2f155d7d5ea02b325cd73e6a85e16266814c765b9bb12649b",+ "208cdb7108c1fe1e3b8cf5b31fabd97de90e24b1b04df207c9961ca27555177dc7ac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38003a7b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a2269726f6e222c22616d74223a223130303030227d68",+ "c08cdb7108c1fe1e3b8cf5b31fabd97de90e24b1b04df207c9961ca27555177dc7"+ ]+ },+ {+ "txid": "772e2d998c73b2396f93e9c025e5918e73486a087ea42a07d89323c9c3fca774",+ "input": 1,+ "height": 840000,+ "scriptBytes": 128,+ "spentScript": "5120c357730f446f102aa3e848ce74e530aa27d1210bbbcecbf1a419445d34025739",+ "witness": [+ "ac6cabd922f3e858735448d5dd705211b79335989ab0d92302c0d8394f0a1da0079f91be69fe8a4d384307ff908ad9db875fd9031a7565b76a151a0e3f6be521",+ "2081142c852bf9a7cd2c5511c335e81367acdd102f5d37e36cd3c518b89127f7b1ac0063036f726401032181763d607e7599dde732fa2c3e7b53ff4c3d097ce89287fc276d8dfe9e88649202010b203e8870d05b6db4cbd949061307f3d0e47366a24c8f60bade59071df05e5b68f80102027803010d08f4b4f0f157e9d21868",+ "c081142c852bf9a7cd2c5511c335e81367acdd102f5d37e36cd3c518b89127f7b1"+ ]+ },+ {+ "txid": "bce9468d2d82ce15939638bbcde8a4c4b8fb9282d46b0abf223b7edd9ff97dcc",+ "input": 0,+ "height": 900000,+ "scriptBytes": 128,+ "spentScript": "5120835979691c2b98abc0a6487c982f747cbff98a63b385f60437ed5c24a7458522",+ "witness": [+ "23b6065ffd6b40ed591dd477fc151bc2f8a1484aeb6654ee9e1cf62f7f987e62e03237c1e3f54e4886527fca136b738a30f7c2fe3efbb50e55dfe3b4bf00f8ce",+ "20739e8e89dfb84872edcb0931728e19622f3476a2d38b240bc88c4b977b273b7cac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38003a7b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226d616f74222c22616d74223a223335393736227d68",+ "c1739e8e89dfb84872edcb0931728e19622f3476a2d38b240bc88c4b977b273b7c"+ ]+ },+ {+ "txid": "c2d8fb1feb8436c4a23ab04522510c6a54f3f8518cec2ff17a6143398bd46e92",+ "input": 0,+ "height": 900000,+ "scriptBytes": 128,+ "spentScript": "51209e231554cba4aa23d53a5323c0ae25de7c37e6473e65669198a5b4cd4c70ad40",+ "witness": [+ "1af798ea16606550f3e8452009dc1c2dadc607a1e1af1f1c8ef0531f530235de1df299f680d5150f69b614c3740f9d6d11b935c9bbe0ea5deb6374a66e40ab97",+ "20258bc1a90a3ed6da478acf8db6bc2d852566ece2399127c725bb9835c80560ceac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38003a7b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a22576c7a78222c22616d74223a22302e313438227d68",+ "c1258bc1a90a3ed6da478acf8db6bc2d852566ece2399127c725bb9835c80560ce"+ ]+ },+ {+ "txid": "e2cc60f881e3f705b5353838708573d7f22acff5c807d8027854a9219c5b094d",+ "input": 1,+ "height": 840000,+ "scriptBytes": 128,+ "spentScript": "5120fea2e969a623d23fc098a749a8ef13c19b0477448c284961dac63b671e57b9e8",+ "witness": [+ "80103dd98ed04f71a4c92030a503825203dd45f5c9f8389f2448aec2c052658fb2ab94cf15784a42e7bf0dcd34f1439c1ff494e465f9b5fc37bd8e76fe0864ad",+ "203323e19281856c11aca6d4e3089b65297dbcb115c45ac32261137e27b78e14e6ac0063036f7264010320633b1a3000e6147c66257a6c4c0279e10ee5bbf1e7838d859feb3172e88307a8010b20633b1a3000e6147c66257a6c4c0279e10ee5bbf1e7838d859feb3172e88307a80102021027010d096b025e08a74a58880268",+ "c13323e19281856c11aca6d4e3089b65297dbcb115c45ac32261137e27b78e14e6"+ ]+ },+ {+ "txid": "7b66f3ca78ae7a5f7d787f8ede379f8d1cd380bda75363c1f5c9dcfcceec31f5",+ "input": 1,+ "height": 840000,+ "scriptBytes": 129,+ "spentScript": "5120a629f7f6867af8e3d8694c7e25ec2b781e56cbf1cf65f038de15fe34c83a0e09",+ "witness": [+ "251d46d96914b732154ca9c1278529e3ca14714770e6a07a2c6d5f5b9d51f9b6e63101a2c875d42a8446fa2f66aac279e25fad797447d670eb7cc111c9e06b0d",+ "20ed247313597e5eadc95a09e6c34643fd4f6edd4dad046cc155d5609c05a81611ac0063036f726401032181763d607e7599dde732fa2c3e7b53ff4c3d097ce89287fc276d8dfe9e88649201010b2064ec82c41727c6dba0d9d61559436a657ae62cdb94462056c297331e868004d40102027803010d09c4102abce829ea8c4168",+ "c0ed247313597e5eadc95a09e6c34643fd4f6edd4dad046cc155d5609c05a81611"+ ]+ },+ {+ "txid": "549e90d115440b53f6153819a271c19252a407b1dedb5a36873cd3047045cf71",+ "input": 0,+ "height": 840000,+ "scriptBytes": 257,+ "spentScript": "51201ddee580cced8b45fb9ad0f9bf026307b9fa187860fb7555460a2ed09c262d81",+ "witness": [+ "2d9249a3dc373f039f8d601ddcd7fb43178c9dce9b2f2803253f8f423a58c92e84ee2e242153d0dd0b98bac274badf49abb8da11cec9bb601b04ca070cd0ba2e",+ "201c3c38c774d0a1bce82aa81acfb2b01cdad69cac59c717ce0ff70399913c8edfac0063036f7264010b20f12c8d3d725d8a2ea0c6cb42ca32fc026e761ac442e2410f46f27cad2688624b01020001054c97a2657469746c6574424954434f494e2057495a415244532052554e456b6465736372697074696f6e786d424954434f494ee280a257495a4152445320697320612072756e65207468617420656d6572676564206173206120686f6d61676520746f207468652069636f6e6963204d6167696320496e7465726e6574204d6f6e6579206d656d6520627920406d6176656e73626f742e200a010d08aa9e34cf3288cf51680063036f7264010202390568",+ "c01c3c38c774d0a1bce82aa81acfb2b01cdad69cac59c717ce0ff70399913c8edf"+ ]+ },+ {+ "txid": "e973a2c700b9db24e0412e93c462a4608668257449f2a5295024789c5aeb5d56",+ "input": 0,+ "height": 840000,+ "scriptBytes": 299,+ "spentScript": "51200bf46fa241831781abdc0cab7fa6c16617d90246b2a9379d591b5f18fcbc5cdb",+ "witness": [+ "5109c3ccd95ece9ec66db9922b546f21a0cdee8d394cd0379be19a128fada9711b6dd2fd4e0e3b6ddd9db2a5a42c8d494e1bc788cf0a69f1927cc069679fe15c",+ "201b08b979750e5aa590fc1550cd275cea759f66e2ef56d2dfbfd5fbbb0e97fb84ac0063036f7264010b2095aea7f0e278f3d7baceb6191fb0d1ac920a6f09ee4d4cb7a88f78a578ff0d3e01020001054ccda16a626974466c6f7765727378bf54686520666972737420426974636f696e20476f6f64732c2061206d696c6573746f6e6520666f7220746865204247502070726f746f636f6c2e2054696d656c65737320466f7274756e652c20466f72676564207769746820426974636f696e2c20466f726576657220426c6f6f6d696e672e2054686520556c74696d617465204769667420457870657269656e63652e536f6c656c792061696d696e6720746f20747265617420796f7520657863657074696f6e616c6c792077656c6c2e010d083b26ccb5c1fbd05168",+ "c01b08b979750e5aa590fc1550cd275cea759f66e2ef56d2dfbfd5fbbb0e97fb84"+ ]+ },+ {+ "txid": "3257291b92c8584dd9b3046d5c6ec0491f708c1459926a237e6ed3fa69564a74",+ "input": 0,+ "height": 840000,+ "scriptBytes": 309,+ "spentScript": "5120f126d35d3aaf88df1052baf48aa525e5d660ae3c50ba8ee7994ce68a20c01cd6",+ "witness": [+ "6e6ae8c7ef99a2ba25c7a8f9b562d56efb0d3c4805d528f899db9f335ece1dff7b8c34f2e24bea06db88a700a2c4c9fe2f78c05378fb50611fe1871092954082",+ "20ec6ee972865a84f7b96186c3486ed99ef0691879cedb8c3713a3ffb4b8fdcab3ac0063036f7264010109696d6167652f676966010b205982ec543d7f10926435eff1816daf297a491dccf794d5fea991bb62b9c4118901020001054cc6a767426974636f696e7077696c6c206e6576657220676f6e6e61704e6576657220676f6e6e61206769766566796f752075706f4e6576657220676f6e6e61206c657468796f7520646f776e764e6576657220676f6e6e612072756e2061726f756e646e616e642064657365727420796f75704e6576657220676f6e6e61206d616b6567796f75206372796f4e6576657220676f6e6e612073617967676f6f64627965764e6576657220676f6e6e612074656c6c2061206c69656c616e64206875727420796f75010d0c2d05ebcb654a9a2a14e15a010068",+ "c1ec6ee972865a84f7b96186c3486ed99ef0691879cedb8c3713a3ffb4b8fdcab3"+ ]+ },+ {+ "txid": "aadf4138a594d388c8412a1fa41f2e5848c084f5b561366eb6394230733f884e",+ "input": 0,+ "height": 840000,+ "scriptBytes": 324,+ "spentScript": "51201bc4bd561b0d28535536bf0e0869fe8dc73afd43ce82d1d7015b765efcc03866",+ "witness": [+ "ad0b2e5a8493f07e9d0003e47e815faeef614f2ba35694332d9264fe8ecf2ddc8f22323bc0369d6130057794965f174be5a5b2867d243e8bedad5e12ce2ccf97",+ "202e8936b0bd8a7eb08e9978c5a28ef1138ce21450e286b8707878297ad6b868fdac0063036f7264010117746578742f68746d6c3b636861727365743d7574662d38004cfe3c6d65746120636f6e74656e743d2277696474683d6465766963652d77696474682c6d696e696d756d2d7363616c653d302e31226e616d653d76696577706f72743e3c626f6479207374796c653d6d617267696e3a303b6865696768743a313030253e3c696d67207372633d2f636f6e74656e742f653734653338643733323464323362313833663635633062396235613637636232363632653739303164393838353835376638316234616535353938323430306930207374796c653d646973706c61793a626c6f636b3b6d617267696e3a6175746f3b696d6167652d72656e646572696e673a706978656c617465643b6865696768743a313030253e68",+ "c12e8936b0bd8a7eb08e9978c5a28ef1138ce21450e286b8707878297ad6b868fd"+ ]+ },+ {+ "txid": "d719670e98d4cd52c20dd712f9bf622ad2f0e22e683bf4f9743c4ef12fd47be5",+ "input": 0,+ "height": 840000,+ "scriptBytes": 324,+ "spentScript": "5120b700d9432c285f7230b942c651c3a264fd75e28b587a049a7a801327265ece89",+ "witness": [+ "78ef4664f8f45b327e2ae1fe042c696cd17c3a86aee80a81ab986cf31a11f9eb4764388e710814ae8e90ccfba350721fc08d896fa303178473458e826ee68fde",+ "206734d8bb4b3df36b54dceeb3ceb96f77b5d5dea8c1771130a66486fe4a2738d7ac0063036f7264010109696d6167652f706e67004d0b0189504e470d0a1a0a0000000d4948445200000018000000180806000000e0773df8000000d24944415478daed943b0ec3200c86197b809e84b997e8c0c8de6364e9d8c374e42cbd0417885211c9c898609b40b722fd8aacc4fefc2036e67f269e0d697e60e75cd64c5011986a14c2061f85a8829f85b4fa5de92ca470fabc97a6e8b75d8018a35a3d80222308605f6b56b2576bb300003e21844504e0d66800e0e7bddf011ce41020490ba86e0d37603c687cabb859a800f41d05a42a86003faf40020c5540ffe4de0a2a0897f5f37e65d7480ba4025008f8241ba49a05d71e10aee471bbec767a0e01e816c5994bc145c0c10a375cefbffe1b9a6b9faa1d040000000049454e44ae42608268",+ "c16734d8bb4b3df36b54dceeb3ceb96f77b5d5dea8c1771130a66486fe4a2738d7"+ ]+ },+ {+ "txid": "e83b52838da0fb8a43248d5a6bac0e5f92a3828ed06fc67fd3f424c105e7c0c5",+ "input": 0,+ "height": 840000,+ "scriptBytes": 328,+ "spentScript": "5120741b3b5468dd9f46d763789af490ec8803613ad10ed0d1cb24dc632c1003b9a8",+ "witness": [+ "e73f5f16726c5d745e436d67557b6932abfaf687c0964512d5686de94ddc2ea7262a547274da9b698adb94ce9614070fe9e1765f0da97f17a3b955a0663873f6",+ "20515d63619a4fa4effa6e40b529ffe5c9ffc7fbf9f59986fa9bf0f3065b5a3598ac0063036f7264010109696d6167652f706e67004d0f0189504e470d0a1a0a0000000d4948445200000018000000180806000000e0773df8000000017352474200aece1ce9000000c949444154484b63dcf077db7f061a02c6510b0885ee6810610d21d90be20c8f0d5e82e5080651a248348621f3df2cc519f420c3610064095e0b40866f7ff50ac3304f3131067c96206bc069012ec3619af159425410516201411f7c4d542394bcc1f2dcf36f911fc9301fdc4d7e08374479ae3c03321f9705047d0052404910d13c0e28b200947ab0015cc996e464aab3b40bc37cd9c21e8644052eb8f845311d86dc6df9f87332be4c46531f1093d1884a45b852122c0e282e2ab059404a1904f3054985ddc8b40000d940ca49ee9c43100000000049454e44ae42608268",+ "c0515d63619a4fa4effa6e40b529ffe5c9ffc7fbf9f59986fa9bf0f3065b5a3598"+ ]+ },+ {+ "txid": "892d79b89349afbd9744ececd2efdf839441ea579fa78d0084b4d1d4865b4d90",+ "input": 0,+ "height": 840000,+ "scriptBytes": 562,+ "spentScript": "51205b7c6fe4be270de2b9b66f0436bfa690faebf723781ffa349b4a6afb411505ea",+ "witness": [+ "633ef1d5bbc92a36679a06c989346ed716428f8c9f4addf5e1e2752007c936c00dc9cadbdd9415cd099e82d38b50c26f76cfb939103c737c1a297aa5b81ec566",+ "20f2d00e1cce0b839d2e197679cac7a15152ec244338032c7767f72c1332d49a65ac0063036f7264010109746578742f68746d6c004c713c736372697074207265746f7264696e616c3d3536207372633d222f636f6e74656e742f353463323038383061633264383939613135306462646235383534346164343262643861313835353537666166316432316237346664616438343761383339396930223e3c2f7363726970743e680063036f726401011e6170706c69636174696f6e2f6a736f6e3b636861727365743d7574662d38004d5d017b2270223a22766f7264222c2276223a312c227479223a22696e7363222c22636f6c223a2235313562353031623537653130306235313862386631646538316539343933303761343663633339353234643939316364396166343562383232346530653564222c22696964223a225265746f7264696e616c73222c227075626c223a2231455337623370636a527a4667796969714c7278484b546e43686535364c4a485937222c226e6f6e6365223a33382c226d696e746572223a2262633170706e3072326b787665786a72676b35776163767061706e733768396378356e36633861717a646b326a70726d7364396b337875736770756b7866222c22736967223a22484a73437074314a4d424b5933503539586437484f58576f4b33716d47657372666b38312b46526d422f386e4770456a317072762b7a6b5873424e3878554e6f564d5743446645517948473274412b564450766e71346f3d227d68",+ "c1f2d00e1cce0b839d2e197679cac7a15152ec244338032c7767f72c1332d49a65675a92934efa5e4869b2432a3e00abfc6819367af43949c2edc693b32d23ac05"+ ]+ },+ {+ "txid": "1230846b9b6d6b6df20c3907aabc486808b73f9632b98820d96c0ce0b464f5c8",+ "input": 0,+ "height": 840000,+ "scriptBytes": 564,+ "spentScript": "51202053fa063fae7fdd9bae16b4e5b1a9b3bfdecb37a4081f16f46ba64ad5fffd61",+ "witness": [+ "2258ee17dc5892894fa0ada2b0d610b66a46ba4d0478e0ac3fc011ffa6a926c20bb6369afab22c4f6a8714d98b576617031eede617f692fb319287c6dea6282b",+ "20f2d00e1cce0b839d2e197679cac7a15152ec244338032c7767f72c1332d49a65ac0063036f7264010109746578742f68746d6c004c723c736372697074207265746f7264696e616c3d363532207372633d222f636f6e74656e742f353463323038383061633264383939613135306462646235383534346164343262643861313835353537666166316432316237346664616438343761383339396930223e3c2f7363726970743e680063036f726401011e6170706c69636174696f6e2f6a736f6e3b636861727365743d7574662d38004d5e017b2270223a22766f7264222c2276223a312c227479223a22696e7363222c22636f6c223a2235313562353031623537653130306235313862386631646538316539343933303761343663633339353234643939316364396166343562383232346530653564222c22696964223a225265746f7264696e616c73222c227075626c223a2231455337623370636a527a4667796969714c7278484b546e43686535364c4a485937222c226e6f6e6365223a3533342c226d696e746572223a22626331703678636a6370737636363068683767776d6e6579683937736c6d706d6336677638646d6578686b37333675726e676772326478717a7a67636579222c22736967223a22484b61747a7649393538762f4467344d38536a4d4e796b5047365563696b79446c70614b4d503445357a574b6556492f37615a54353741476a735a78647153726c466a517935744b73454f42586f4a7878757377446d593d227d68",+ "c1f2d00e1cce0b839d2e197679cac7a15152ec244338032c7767f72c1332d49a65675a92934efa5e4869b2432a3e00abfc6819367af43949c2edc693b32d23ac05"+ ]+ },+ {+ "txid": "15026593a1d763984038102b03fe603c3b8ba7c00f37db886a392b4d1faf1cc6",+ "input": 0,+ "height": 840000,+ "scriptBytes": 564,+ "spentScript": "5120d7532ec5070903d66b713ed5ef1f27b52ace24a27ff989d0ea0c4a9a3b923db0",+ "witness": [+ "2b6681984894706428a162d7eb6ee977d688e2d739e9bd2c9456fdbc8d8844b430b37a5c787006896bf62355adc576c9588f5d6dd5902eab40d97b2fa6a54e47",+ "20f2d00e1cce0b839d2e197679cac7a15152ec244338032c7767f72c1332d49a65ac0063036f7264010109746578742f68746d6c004c723c736372697074207265746f7264696e616c3d363238207372633d222f636f6e74656e742f353463323038383061633264383939613135306462646235383534346164343262643861313835353537666166316432316237346664616438343761383339396930223e3c2f7363726970743e680063036f726401011e6170706c69636174696f6e2f6a736f6e3b636861727365743d7574662d38004d5e017b2270223a22766f7264222c2276223a312c227479223a22696e7363222c22636f6c223a2235313562353031623537653130306235313862386631646538316539343933303761343663633339353234643939316364396166343562383232346530653564222c22696964223a225265746f7264696e616c73222c227075626c223a2231455337623370636a527a4667796969714c7278484b546e43686535364c4a485937222c226e6f6e6365223a3531302c226d696e746572223a22626331707a6a68667a6e77656172767873716c786b796a73356b6635396d326139377764326c3464783874666b6b676171736461666b75737564786a6d6a222c22736967223a22473167755a334d7747765a707145347351766a68324d68686478482f444a515048635333334c514666473647472f5239436c473134355374766a626144326150394d5a74433938387744323748547949756c4e587533413d227d68",+ "c1f2d00e1cce0b839d2e197679cac7a15152ec244338032c7767f72c1332d49a65675a92934efa5e4869b2432a3e00abfc6819367af43949c2edc693b32d23ac05"+ ]+ },+ {+ "txid": "d5a7d82af0d390bc175ef349db1cb68d2e953e4ff0baf23cca484f37d3f909f9",+ "input": 0,+ "height": 840000,+ "scriptBytes": 531,+ "spentScript": "5120675033149d4e7a75c460cc0d6fb91c4c1faffa4201e06f787ef34317e7d9c5e2",+ "witness": [+ "fd9209780edecf1face42577a0d8075b92d379dc96f1a12cd4065f6213da19c5b4a77273b3aaf7ad5d3f677091b771209e6c9d89382fdc33edbb0b02b4675a34",+ "20be11c34ef16429688176b463333e09a20906c0ba7bfb28e9b18eb80a4787bfccac0063036f72645d08fe9fbf7864d36e22010109696d6167652f706e67004dd00189504e470d0a1a0a0000000d4948445200000080000000800103000000f9f0f3880000000467414d410000b18f0bfc6105000000017352474200aece1ce90000000970485973000012740000127401de661f7800000006504c5445000000000000a567b9cf0000000274524e53f801ddf1961b000001454944415448c7edd4418aa0301484e13f6491a547c851ec9bf45192a379941c21cb30486a167931ea0c0c03bd6a5a90920f1113eb895e073ff09de0dcc6754d06c7043fa033812421b5059b8454160409292f7012ea2c20099d77d8851a6ebe54260a55e284c6265448133a41e8706b218717ca61417142eb11524374f6052789733d53eaec9c8f2d62a7b93be448f3773822f50165a3840704ca7687fa861638e203fc843a6e3c3dc7fe0047fe03d21dfaff83c056ffe550b7757e0de47487b1967fc36bf9ef2d7c6df2df3ec3039a9f9f724278410db30e5750edcacfc258a50e3f2b65a5cb6e96ce6a697bcb6ec5ed58ec56ed138b64e56f63ce1a733c8ab3980394bd858d58c71792c480a8c65647d8986662c3e983380619d82dc6a803c962fc0cc05d81548070055205a2d4462075205d81a48cd715483ac72cffe2f3e737feade137d92bc6203349d4e80000000049454e44ae42608268",+ "c1be11c34ef16429688176b463333e09a20906c0ba7bfb28e9b18eb80a4787bfcc"+ ]+ },+ {+ "txid": "ec357ff474e061b1f58fe193cd30d77238a0c64c3058e4bf250830f719ab4d20",+ "input": 0,+ "height": 840000,+ "scriptBytes": 588,+ "spentScript": "512042e74a371e2bf52f07848ff7345aa2109d64af0e16ac09e826b1326c4a2451ca",+ "witness": [+ "d582c40703c3a0ff65bf2320857a8e5dfc7353777c5e933df90f60d332e6176d3de8e509bc4774ae1a04b989c64265e44865175d03e5b5f3fd3c4336a20396cb",+ "2016046b966266c4bbaa542549fe5f0a4d37493bac038100dd7698d8e8fd4f041bac0063036f72645d08231d4fc402b4250301010d696d6167652f7376672b786d6c004d05023c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f7376672220786d6c3a73706163653d227072657365727665222066696c6c3d226e6f6e6522207374796c653d2277696474683a313030253b6865696768743a31303025222076696577426f783d22302030203332203332223e3c7061746820643d224d30203068333276333248307a22207374796c653d2266696c6c3a233030303b7374726f6b652d77696474683a35302e373137353b7374726f6b652d6c696e656361703a726f756e64222f3e3c7061746820643d226d3133322e323238203130342e3432342e3531362e373935682e3331386c2d2e37383820312e32363168312e3032386c2e3738382d312e3236682e386c2d2e38393720312e3433342e38393720312e343334682d2e386c2d2e3738382d312e3236682d312e3032386c2e37383820312e3236682d2e3331386c2d2e3531362e37393668332e3139326c2e3439392d2e3736392d2e3931342d312e3436312e3931342d312e3436322d2e352d2e3736387a22207374796c653d2266696c6c3a236666663b7374726f6b652d77696474683a332e32313034383b7374726f6b652d6c696e656361703a726f756e6422207472616e73666f726d3d227472616e736c617465282d3439302e343332202d3338372e3129207363616c6528332e373739353329222f3e3c2f7376673e68",+ "c116046b966266c4bbaa542549fe5f0a4d37493bac038100dd7698d8e8fd4f041b"+ ]+ },+ {+ "txid": "02fa364c362d25d3081f65afdb8887ee00eb1ad370965c421d85cdb584521b45",+ "input": 0,+ "height": 840000,+ "scriptBytes": 635,+ "spentScript": "5120d6fcd726c160a99327c818135e7bb8bdf12e17e3d91fdf353db2e7e7c3188e23",+ "witness": [+ "b84c633db29bc13e7d27b0608b98c292c0934f770404cb936c56b4ec0be49f2ff2aef56e039832555e42fc9600e5c9e559e1685d05567bf5d8a740ef5437501a",+ "2052d184e797a4127e473b71410f01ce7a34e88dfaf4c8af5c7e055e01b287f5b5ac0063036f72645d0cc59bbde6585900d4ff67ef01010109696d6167652f706e67004d080289504e470d0a1a0a0000000d494844520000003d000000140803000000091fc5250000000373424954080808dbe14fe00000004b504c5445fffffff6f6f6fcfcfc8f8f8facacacc6c6c6e6e6e6000000c1c1c1a4a4a4ddddddb2b2b2979797eaeaead0d0d0d6d6d6121212efefef8383836a6a6af3f3f3bababa2323235555552a2a2ae1b303820000019449444154388da592eb72eb200c8405182f6082c1b7f4fd9fb40bb84e33cd999c4ef941e4b01f12d28afc75e949dcf2faa8e02d3d43867fa8547c4b031810e1451210c41bd811702df72a015814431b4189f630b7277a42188009e388c9e76d850f307bc3470896542f667991420f87e73ae7a3558e7a7b2d63919419ac9d5e61666974956012cdfa5ed08e42632ca24c9981efb4f2407cd0b60abfd3219fb4c5a65959149567567c56ee64c18386b6787af8963f3aad17162c3513f335cd2db38ee348173dded1baf062e9b7e3f9f5b2f5053f5d13d27fd15305c71f3486f6a3f8c451ba3d668e5d8a8157027ffebd767ab81b7daa38586bd1c7ee906aabccfee14a6653f9550d51f795fb8e4e03879366224a220a5ce9b4b33a559f384761086c76826665a0e626a6d3854157d1ba93ba86ce4cb4a9e3da292c018a6285243b6a3c7cbddb9f2abeefe0f96519c78f6a0f9583a664a8f91acdf4fade69a7d98da662858af7fa2b354e7bf8eaeeda9074d2fd90348dbf6e5dc5fba88a182b7af65b73c9d62750b7f2188cbee2edb117f5a5fcd3fa044cff126976b605840000000049454e44ae42608268",+ "c052d184e797a4127e473b71410f01ce7a34e88dfaf4c8af5c7e055e01b287f5b5"+ ]+ }+ ]+}
haskoin-core.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: haskoin-core-version: 1.5.1+version: 1.6.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@@ -19,7 +19,9 @@ extra-source-files: data/block_100000.hex data/bip341.json+ data/bip39_vectors.json data/complex_psbt.json+ data/core_signed_txs.json data/forkid_script_tests.json data/forkid_sighash.json data/key_io_invalid.json@@ -27,6 +29,7 @@ data/psbt_bip174.json data/psbt_bip370.json data/psbt_bip371.json+ data/psbt_complex_core32.json data/psbt_core32.json data/rfc6979cash.json data/rfc6979core.json@@ -36,6 +39,11 @@ data/sig_nonstrict.json data/sig_strict.json data/sighash.json+ data/slip39_split.json+ data/slip39_vectors.json+ data/taproot_mainnet_spends.json+ data/bip39_wordlist.txt+ data/slip39_wordlist.txt data/test_vectors_aserti3-2d_run01.txt data/test_vectors_aserti3-2d_run02.txt data/test_vectors_aserti3-2d_run03.txt@@ -72,6 +80,7 @@ Haskoin.Crypto.Keys.Common Haskoin.Crypto.Keys.Extended Haskoin.Crypto.Keys.Mnemonic+ Haskoin.Crypto.Keys.Slip39 Haskoin.Crypto.Signature Haskoin.Network Haskoin.Network.BlockFilter@@ -141,6 +150,7 @@ , text >=1.2.3.0 , time >=1.9.3 , transformers >=0.5.6.2+ , unicode-data >=0.2 , unicode-transforms >=0.4 , unordered-containers >=0.2.10.0 , vector >=0.12.1.2@@ -157,6 +167,7 @@ Haskoin.Crypto.HashSpec Haskoin.Crypto.Keys.ExtendedSpec Haskoin.Crypto.Keys.MnemonicSpec+ Haskoin.Crypto.Keys.Slip39Spec Haskoin.Crypto.KeysSpec Haskoin.Crypto.SignatureSpec Haskoin.NetworkSpec@@ -203,6 +214,7 @@ , text >=1.2.3.0 , time >=1.9.3 , transformers >=0.5.6.2+ , unicode-data >=0.2 , unicode-transforms >=0.4 , unordered-containers >=0.2.10.0 , vector >=0.12.1.2
src/Haskoin/Address.hs view
@@ -64,6 +64,7 @@ p2wpkhAddr, p2shAddr, p2wshAddr,+ p2shP2wpkhAddr, inputAddress, outputAddress, addressToScript,@@ -82,7 +83,7 @@ import Control.Applicative (Alternative ((<|>))) import Control.DeepSeq (NFData)-import Control.Monad (guard, when, (<=<))+import Control.Monad (guard, mfilter, when, (<=<)) import Crypto.Secp256k1 import Data.Aeson (ToJSON (toJSON), withText) import Data.Aeson.Encoding (text)@@ -99,6 +100,7 @@ import Data.Text qualified as T import Data.Word (Word8) import GHC.Generics (Generic)+import GHC.Read (expectP) import Haskoin.Address.Base58 import Haskoin.Address.Bech32 import Haskoin.Address.CashAddr@@ -108,6 +110,17 @@ import Haskoin.Script.Common import Haskoin.Script.Standard import Haskoin.Util+import Text.Read+ ( Lexeme (Ident, Punc),+ Read (readListPrec, readPrec),+ ReadPrec,+ lexP,+ parens,+ pfail,+ prec,+ readListPrecDefault,+ reset,+ ) -- | Address format for Bitcoin and Bitcoin Cash. --@@ -115,7 +128,18 @@ -- 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.+-- representation, so two addresses with the same text are always equal.+--+-- Two different addresses can still pay the same script. A token-aware+-- CashAddr (type 2 or 3) locks coins with the script of its plain form (type+-- 0 or 1), and the two are not equal. To find what an address was paid, look+-- it up by the script of 'addressToOutput', not by the address.+--+-- A value does not know its network. 'textToAddr' and the JSON parser only+-- return addresses of the network they are given. The constructors, 'Read'+-- and the binary form do not check that, so a value from one of those can be+-- of a kind its chain does not have. 'addrToText' gives 'Nothing' for such a+-- value. data Address = -- | pay to public key hash (regular) PubKeyAddress@@ -157,8 +181,41 @@ bytes :: !ByteString } deriving- (Eq, Ord, Generic, Show, Read, Hashable, NFData)+ (Eq, Ord, Generic, Show, Hashable, NFData) +-- | Reads the text that 'Show' writes. The two generic kinds go through+-- 'witnessAddress' and 'cashAddress', so a version or a length that those+-- refuse does not read, and neither does a value that has a dedicated+-- constructor.+instance Read Address where+ readPrec =+ parens . prec 11 $ do+ Ident name <- lexP+ expectP (Punc "{")+ addr <- case name of+ "PubKeyAddress" -> PubKeyAddress <$> field "hash160"+ "ScriptAddress" -> ScriptAddress <$> field "hash160"+ "WitnessPubKeyAddress" -> WitnessPubKeyAddress <$> field "hash160"+ "WitnessScriptAddress" -> WitnessScriptAddress <$> field "hash256"+ "Script32Address" -> Script32Address <$> field "hash256"+ "OtherWitnessAddress" -> generic isWitnessAddress witnessAddress+ "OtherCashAddress" -> generic isCashAddress cashAddress+ _ -> pfail+ expectP (Punc "}")+ return addr+ where+ field :: (Read a) => String -> ReadPrec a+ field name = do+ expectP (Ident name)+ expectP (Punc "=")+ reset readPrec+ generic isKind build = do+ v <- field "version"+ expectP (Punc ",")+ b <- field "bytes"+ maybe pfail return (mfilter isKind (build v b))+ readListPrec = readListPrecDefault+ -- | Match a SegWit address with a witness version other than zero. Use -- 'witnessAddress' to build one. pattern WitnessAddress :: Word8 -> ByteString -> Address@@ -422,6 +479,11 @@ -- 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.+--+-- An address from 'outputAddress', 'scriptToAddress', 'scriptToAddressBS',+-- 'textToAddr' or the JSON parser of the same network always has a text form+-- there. One from a constructor or from the binary form may not. Check such+-- a value with 'addrToText' before encoding it. instance MarshalJSON Network Address where marshalValue net = toJSON . addrTextOrError net marshalEncoding net = text . addrTextOrError net@@ -477,6 +539,16 @@ _ -> Nothing -- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network.+--+-- The text must be the address and nothing else. White space around it is+-- refused, where Bitcoin Core skips it around a 'Base58' address, so trim+-- text that comes from a user.+--+-- A result is a well-formed address of the network. It is not always one+-- that can be paid. A CashAddr of a type above 3, or with a hash that is not+-- 20 or 32 bytes long, decodes to an address that has no output script, and+-- Bitcoin Cash Node refuses it as a destination. 'addressToOutput' tells:+-- it gives 'Nothing' for those. textToAddr :: Network -> Text -> Maybe Address textToAddr net txt = cashToAddr net txt <|> bech32ToAddr net txt <|> base58ToAddr net txt@@ -539,7 +611,7 @@ pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Maybe Address pubKeyCompatWitnessAddr ctx pk = do guard pk.compress- return . payToScriptAddress ctx . PayWitnessPKHash $ pubKeyHash ctx pk+ return . p2shP2wpkhAddr $ pubKeyHash ctx pk -- | Obtain a standard pay-to-public-key-hash (P2PKH) address from a 'Hash160'. p2pkhAddr :: Hash160 -> Address@@ -558,23 +630,60 @@ p2wshAddr :: Hash256 -> Address p2wshAddr = WitnessScriptAddress +-- | Obtain the backwards-compatible SegWit P2SH-P2WPKH address of the+-- 'Hash160' of a compressed public key. It is the pay-to-script-hash address+-- of the witness program @OP_0@ followed by the push of the 20-byte hash,+-- which is 22 bytes long.+p2shP2wpkhAddr :: Hash160 -> Address+p2shP2wpkhAddr h =+ p2shAddr . addressHash $ B.pack [0x00, 0x14] <> hashBytes h+ -- | Compute a standard pay-to-script-hash (P2SH) address for an output script.-payToScriptAddress :: Ctx -> ScriptOutput -> Address-payToScriptAddress ctx = p2shAddr . addressHash . marshal ctx+--+-- Fails when the redeem script is longer than 'maxRedeemScriptSize' (520)+-- bytes. Consensus limits a push to that size, and the redeem script is a+-- push of the spending input, so coins sent to such an address could never be+-- spent. A multisig of compressed keys fits up to 15 keys. Use+-- 'payToWitnessScriptAddress' or 'payToNestedScriptAddress' for more.+--+-- Also fails for a value that 'encodeOutput' cannot write, such as a multisig+-- that requires more signatures than it has keys.+payToScriptAddress :: Ctx -> ScriptOutput -> Either String Address+payToScriptAddress ctx =+ fmap p2shAddr . p2shScriptHash <=< encodeOutput ctx -- | Compute a SegWit pay-to-witness-script-hash (P2WSH) address for an output -- script.-payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Address-payToWitnessScriptAddress ctx = p2wshAddr . sha256 . marshal ctx+--+-- Fails when the witness script is longer than 'maxWitnessScriptSize'+-- (10,000) bytes, which consensus could never execute, and for a value that+-- 'encodeOutput' cannot write. The relay policy of Bitcoin Core is stricter+-- than consensus: it does not relay the spend of a witness script above 3,600+-- bytes, nor one with an uncompressed key. This function does not check+-- either.+payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Either String Address+payToWitnessScriptAddress ctx =+ fmap p2wshAddr . p2wshScriptHash <=< encodeOutput ctx --- | Compute a backwards-compatible SegWit P2SH-P2WSH address.-payToNestedScriptAddress :: Ctx -> ScriptOutput -> Address-payToNestedScriptAddress ctx =- p2shAddr . addressHash . marshal ctx . PayWitnessScriptHash . sha256 . marshal ctx+-- | Compute a backwards-compatible SegWit P2SH-P2WSH address: the+-- pay-to-script-hash address of the witness program of the script. Fails as+-- 'payToWitnessScriptAddress' does.+payToNestedScriptAddress :: Ctx -> ScriptOutput -> Either String Address+payToNestedScriptAddress ctx out = do+ h <- p2wshScriptHash =<< encodeOutput ctx out+ payToScriptAddress ctx (PayWitnessScriptHash h) -- | 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'.+--+-- The function does not know the network. It gives the script of a SegWit+-- address or of a 256-bit script hash address whatever chain the caller+-- works on. An address read with 'textToAddr' or from JSON always belongs to+-- its network. An address built with a constructor, or read from its binary+-- form, may not: check it with 'addrToText' of the network before paying it.+-- On Bitcoin, anyone who sees the redeem script can spend the script of a+-- 'Script32Address', and Bitcoin Cash does not have SegWit. addressToOutput :: Address -> Maybe ScriptOutput addressToOutput = \case@@ -605,39 +714,65 @@ addressToScriptBS :: Ctx -> Address -> Maybe ByteString addressToScriptBS ctx addr = (runPutS . serialize) <$> addressToScript ctx addr --- | Decode an output script into an 'Address' if it has such representation.-scriptToAddress :: Ctx -> Script -> Either String Address-scriptToAddress ctx =- maybeToEither e . outputAddress ctx <=< decodeOutput ctx+-- | Decode an output script into the 'Address' it has on a network, if it+-- has one there. See 'outputAddress'.+scriptToAddress :: Network -> Ctx -> Script -> Either String Address+scriptToAddress net ctx =+ maybeToEither e . outputAddress net ctx <=< decodeOutput ctx where e = "Could not decode address" --- | Decode a serialized script into an 'Address'.-scriptToAddressBS :: Ctx -> ByteString -> Either String Address-scriptToAddressBS ctx =- maybeToEither e . outputAddress ctx <=< unmarshal ctx+-- | Decode a serialized script into the 'Address' it has on a network, if it+-- has one there. See 'outputAddress'.+scriptToAddressBS :: Network -> Ctx -> ByteString -> Either String Address+scriptToAddressBS net ctx =+ maybeToEither e . outputAddress net ctx <=< unmarshal ctx where e = "Could not decode address" --- | 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 _ =+-- | Get the 'Address' that a 'ScriptOutput' has on a network.+--+-- 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.+--+-- An output only has an address on a network that defines its kind. A+-- witness program has none on a network without SegWit, such as Bitcoin+-- Cash. A 256-bit script hash (@OP_HASH256@, a push of 32 bytes, @OP_EQUAL@)+-- has one on the Bitcoin Cash networks only. On Bitcoin that script is a+-- plain hash lock, which Bitcoin Core reports as non-standard. Every address+-- this function returns has a text form on the network, so 'addrToText' and+-- the JSON encoder succeed for it.+outputAddress :: Network -> Ctx -> ScriptOutput -> Maybe Address+outputAddress net _ = \case PayPKHash h -> Just $ PubKeyAddress h PayScriptHash h -> Just $ ScriptAddress h- PayWitnessPKHash h -> Just $ WitnessPubKeyAddress h- PayWitnessScriptHash h -> Just $ WitnessScriptAddress h- PayWitness v d -> witnessAddress v d- PayScript32Hash h -> Just $ Script32Address h+ PayWitnessPKHash h -> witness $ Just (WitnessPubKeyAddress h)+ PayWitnessScriptHash h -> witness $ Just (WitnessScriptAddress h)+ PayWitness v d -> witness $ witnessAddress v d+ PayScript32Hash h -> script32 $ Just (Script32Address h) _ -> Nothing+ where+ -- SegWit addresses exist where the network has a Bech32 prefix.+ witness a = guard (isJust net.bech32Prefix) >> a+ -- Pay-to-script-hash with a 256-bit hash exists where the network has+ -- CashAddr.+ script32 a = guard (isJust net.cashAddrPrefix) >> a --- | Infer the 'Address' of a 'ScriptInput'.+-- | Infer the 'Address' that a 'ScriptInput' spends from. It is the address+-- of the public key or of the redeem script that the input carries.+--+-- 'decodeInput' only returns a redeem script in the form 'encodeOutput'+-- writes, so for a decoded input this is the hash of the redeem script bytes+-- of the input. There is no address for a redeem script above+-- 'maxRedeemScriptSize' bytes, since no valid input can carry one. The+-- function does not look at the output being spent, so it is a guess that a+-- caller should confirm against that output when it has it. inputAddress :: Ctx -> ScriptInput -> Maybe Address inputAddress ctx = \case (RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr ctx key- (ScriptHashInput _ rdm) -> Just $ payToScriptAddress ctx rdm- (NestedWitnessInput rdm) -> Just $ payToScriptAddress ctx rdm+ (ScriptHashInput _ rdm) -> eitherToMaybe $ payToScriptAddress ctx rdm+ (NestedWitnessInput rdm) -> eitherToMaybe $ payToScriptAddress ctx rdm _ -> Nothing
src/Haskoin/Address/Bech32.hs view
@@ -175,6 +175,9 @@ bech32Encode :: Bech32Encoding -> HRP -> [Word5] -> Maybe Bech32 bech32Encode enc hrp dat = do guard $ checkHRP hrp+ -- The constructor of 'Word5' can hold any byte, and only 0 to 31 have a+ -- character.+ guard $ all (\(UnsafeWord5 w) -> w <= 31) dat let dat' = dat ++ bech32CreateChecksum enc (T.toLower hrp) dat rest = map (charset !) dat' result = T.concat [T.toLower hrp, T.pack "1", T.pack rest]
src/Haskoin/Address/CashAddr.hs view
@@ -44,7 +44,7 @@ import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.ByteString.Char8 qualified as C-import Data.Char (isAscii, ord)+import Data.Char (isAscii, isAsciiLower, isAsciiUpper, ord) import Data.List (elemIndex, foldl') import Data.Maybe (fromMaybe) import Data.Text (Text)@@ -78,10 +78,31 @@ checkSumLength :: Int checkSumLength = 8 --- | Get the 32-bit number associated with this 'Cash32' character.-base32char :: Char -> Maybe Word8-base32char = fmap fromIntegral . (`elemIndex` charset)+-- | An ASCII letter of either case.+isAsciiLetter :: Char -> Bool+isAsciiLetter c = isAsciiLower c || isAsciiUpper c +-- | The longest data part of a 'CashAddr', in characters, checksum included.+-- The largest hash is 64 bytes. With the version byte that is 65 bytes, or+-- 520 bits, which take 104 characters of five bits. The checksum adds+-- 'checkSumLength' characters: 104 + 8 = 112.+maxCashAddrDataLength :: Int+maxCashAddrDataLength = 112++-- | The value of each byte as a 'Cash32' character, or 0xff for a byte that+-- is not one. Only lowercase letters and digits are in the table.+base32table :: ByteString+base32table =+ B.pack [maybe 0xff fromIntegral (elemIndex (toEnum i) charset) | i <- [0 .. 255]]++-- | Turn lowercase 'Cash32' characters into their five-bit values. Fails if+-- a byte is not a 'Cash32' character.+base32values :: ByteString -> Maybe ByteString+base32values dat = do+ let values = B.map (B.index base32table . fromIntegral) dat+ guard (B.all (/= 0xff) values)+ return values+ -- | 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.@@ -90,9 +111,14 @@ -- | High-Level: decode 'CashAddr' string if it is valid for the -- provided 'Network'. Prefix may be omitted from the string.+--+-- A string longer than the prefix of the network, the colon and+-- 'maxCashAddrDataLength' characters is refused before any other work. No+-- valid address is longer, since the largest hash the format has is 64 bytes. cashAddrDecode :: Network -> CashAddr -> Maybe (CashVersion, ByteString) cashAddrDecode net ca = do epfx <- net.cashAddrPrefix+ guard (T.length ca <= T.length epfx + 1 + maxCashAddrDataLength) guard (singleCase ca) let (cpfx, cdat) = T.breakOnEnd ":" (T.toLower ca) guard (T.null cpfx || T.init cpfx == epfx)@@ -127,13 +153,18 @@ let len = B.length bs vb <- encodeVersionByte cv len let pl = vb `B.cons` bs- return (cash32encode pfx pl)+ cash32encode pfx pl -- | Low-Level: decode 'Cash32' string. 'CashPrefix' must be part of the string. -- 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.+--+-- The prefix must be one or more letters, and the string has one colon, as+-- in the decoder of Bitcoin Cash Node. There is no limit on the length here,+-- since the data is arbitrary. The time and the memory grow in proportion to+-- the length. cash32decode :: Cash32 -> Maybe (CashPrefix, ByteString) cash32decode text = do (pfx, groups) <- cash32decodeGroups text@@ -150,7 +181,9 @@ if B.null pfx' || pfx' == C.singleton ':' then Nothing else Just (B.init pfx')- b32 <- B.pack <$> mapM base32char (C.unpack dat)+ -- Letters only: no digit and no second colon. The text is lowercase here.+ guard (C.all isAsciiLower pfx)+ b32 <- base32values dat guard (B.length b32 >= checkSumLength) let px = B.map (.&. 0x1f) pfx pd = px <> B.singleton 0 <> b32@@ -159,16 +192,22 @@ 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.-cash32encode :: CashPrefix -> ByteString -> Cash32-cash32encode pfx bs =+-- arbitrary data, of any length.+--+-- The prefix must be one or more ASCII letters, which is what 'cash32decode'+-- and Bitcoin Cash Node read back. It is written in lowercase, so that the+-- string is never of mixed case. Any other prefix fails.+cash32encode :: CashPrefix -> ByteString -> Maybe Cash32+cash32encode prefix bs = do+ guard (not (T.null prefix) && T.all isAsciiLetter prefix)+ let pfx = T.toLower prefix let b32 = toBase32 bs px = B.map (.&. 0x1f) (encodeUtf8 pfx) pd = px <> B.singleton 0 <> b32 <> B.replicate checkSumLength 0 cs = cash32Polymod pd c32 = B.map f (b32 <> cs) f = fromIntegral . ord . (charset !!) . fromIntegral- in pfx <> ":" <> decodeUtf8 c32+ return (pfx <> ":" <> decodeUtf8 c32) -- | Convert base of 'ByteString' from eight bits per byte to five bits per -- byte, adding padding as necessary.
src/Haskoin/Block/Common.hs view
@@ -291,11 +291,13 @@ } deriving (Eq, Show, Read, Generic, NFData) +-- | A locator of more than 'maxLocatorSize' hashes fails the parse. Bitcoin+-- Core and Bitcoin Cash Node disconnect a peer that sends one. instance Serial GetBlocks where deserialize = GetBlocks <$> getWord32le- <*> getVarIntList deserialize+ <*> getVarIntListMax "getblocks" maxLocatorSize deserialize <*> deserialize serialize (GetBlocks v xs h) = do putWord32le v@@ -320,11 +322,13 @@ } deriving (Eq, Show, Read, Generic, NFData) +-- | A locator of more than 'maxLocatorSize' hashes fails the parse. Bitcoin+-- Core and Bitcoin Cash Node disconnect a peer that sends one. instance Serial GetHeaders where deserialize = GetHeaders <$> getWord32le- <*> getVarIntList deserialize+ <*> getVarIntListMax "getheaders" maxLocatorSize deserialize <*> deserialize serialize (GetHeaders v xs h) = do putWord32le v@@ -350,8 +354,12 @@ } deriving (Eq, Show, Read, Generic, NFData) +-- | A message of more than 'maxHeadersSize' headers fails the parse, as soon+-- as the count is read. Bitcoin Core and Bitcoin Cash Node check the count+-- before they read the headers, and punish a peer that sends more. instance Serial Headers where- deserialize = Headers <$> getVarIntList (liftM2 (,) deserialize deserialize)+ deserialize =+ Headers <$> getVarIntListMax "headers" maxHeadersSize (liftM2 (,) deserialize deserialize) serialize (Headers xs) = putVarIntList (\(a, b) -> serialize a >> serialize b) xs
src/Haskoin/Block/Headers.hs view
@@ -84,7 +84,7 @@ where import Control.DeepSeq-import Control.Monad (mzero, unless, when)+import Control.Monad (guard, 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@@ -135,13 +135,20 @@ -- | skip magic block hash skip :: !BlockHash }- deriving (Show, Read, Generic, Hashable, NFData)+ deriving (Show, Read, Generic, NFData) +-- | Hashes the header only, so that two nodes that are equal hash alike.+instance Hashable BlockNode where+ hashWithSalt salt = hashWithSalt salt . (.header)+ instance Serial BlockNode where deserialize = do header <- deserialize height <- getWord32le work <- getInteger+ -- Chain work is a 256-bit unsigned number in Bitcoin Core.+ when (work < 0 || work >= 2 ^ (256 :: Int)) $+ fail "BlockNode: chain work out of range" if height == 0 then do let skip = headerHash header@@ -224,11 +231,15 @@ addBlockHeaderMemory :: BlockNode -> HeaderMemory -> HeaderMemory addBlockHeaderMemory bn s = s {blocks = addBlockToMap bn s.blocks} --- | Get block header from memory block map.+-- | Get block header from memory block map. The map is keyed by eight bytes+-- of the hash, so the node found there is returned only when its header has+-- the hash that was asked for. getBlockHeaderMemory :: BlockHash -> HeaderMemory -> Maybe BlockNode getBlockHeaderMemory bh s = do bs <- shortBlockHash bh `HashMap.lookup` s.blocks- eitherToMaybe . runGetS deserialize $ fromShort bs+ node <- eitherToMaybe . runGetS deserialize $ fromShort bs+ guard (headerHash node.header == bh)+ return node -- | Calculate short block hash taking eight non-zero bytes from the 16-byte -- hash. This function will take the bytes that are not on the zero-side of the@@ -298,6 +309,23 @@ -- 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.+--+-- A header that is already in the store is not validated again: its stored+-- node is returned, as @AcceptBlockHeader@ does in Bitcoin Core.+--+-- What bounds the headers a peer can add to the store:+--+-- * Once the best block has reached a checkpoint, a new header at or below+-- that height is refused.+-- * Once the best block has the 'minimumChainWork' of the network, a new+-- header whose chain has less work is refused. Such a header can only fork+-- from far below the tip, where proof of work was cheap.+--+-- Neither applies while the node is still syncing below them. Bitcoin Core+-- stores no header from a peer before its chain has shown the minimum work.+-- This function has no such phase, so a caller that syncs from untrusted+-- peers should not keep headers for good before the best block has that+-- work. connectBlocks :: (BlockHeaders m) => Network ->@@ -357,6 +385,19 @@ BlockHeader -> m (Either String BlockNode) connectHeader net t best bh =+ getBlockHeader (headerHash bh) >>= \case+ Just known -> return (Right known)+ Nothing -> connectNewHeader net t best bh++-- | Validate a header that is not in the store yet, and add it.+connectNewHeader ::+ (BlockHeaders m) =>+ Network ->+ Timestamp ->+ BlockNode ->+ BlockHeader ->+ m (Either String BlockNode)+connectNewHeader net t best bh = runExceptT $ do par <- maybeToExceptT@@ -417,6 +458,11 @@ unless (validTimeWarp net ng par.header.timestamp nt) $ Left $ "Block timestamp too early for retarget: " ++ show nt+ -- Once the best chain has the minimum chain work, a header with less can+ -- only be on a fork from far below the tip.+ unless (aw >= net.minimumChainWork || bb.work < net.minimumChainWork) $+ Left $+ "Header chain has less work than the minimum chain work: " ++ show ng unless (afterLastCP net (bb.height) ng) $ Left $ "Rewriting pre-checkpoint chain: " ++ show ng@@ -527,9 +573,13 @@ | height /= net.bip34Height = True | otherwise = maybe True (== hsh) net.bip34Hash --- | 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.+-- | Check if the provided block height and version are valid. The three+-- conditions are independent, as in @ContextualCheckBlockHeader@ of Bitcoin+-- Core and Bitcoin Cash Node: a version below 2 is refused from the BIP34+-- height, below 3 from the BIP66 height and below 4 from the BIP65 height,+-- whatever the order of those heights on the network. The version is a+-- signed 32-bit number, so values with the top bit set are negative and+-- count as below version 2. validVersion :: Network -> -- | new child height@@ -537,11 +587,12 @@ -- | new child version Word32 -> Bool-validVersion net height version- | signedVersion < 2 = height < net.bip34Height- | signedVersion < 3 = height < net.bip66Height- | signedVersion < 4 = height < net.bip65Height- | otherwise = True+validVersion net height version =+ not+ ( (signedVersion < 2 && height >= net.bip34Height)+ || (signedVersion < 3 && height >= net.bip66Height)+ || (signedVersion < 4 && height >= net.bip65Height)+ ) where signedVersion = fromIntegral version :: Int32 @@ -957,18 +1008,15 @@ diffInterval :: Network -> Word32 diffInterval net = net.targetTimespan `div` net.targetSpacing --- | 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.+-- | Compare two blocks to get the best: the one with more accumulated work.+-- When the work is equal the first argument is kept, whatever the heights,+-- so callers pass the block they already have first and the block that was+-- seen first stays the best. This is the order of+-- @CBlockIndexWorkComparator@ in Bitcoin Core: chain work, then arrival. chooseBest :: BlockNode -> BlockNode -> BlockNode chooseBest b1 b2- | b1.work == b2.work =- if b1.height >= b2.height- then b1- else b2- | b1.work > b2.work = b1- | otherwise = b2+ | b2.work > b1.work = b2+ | otherwise = b1 -- | Get list of blocks for a block locator: the ten blocks below the best, then -- blocks at exponentially growing distances, then genesis. This is
src/Haskoin/Block/Merkle.hs view
@@ -24,6 +24,9 @@ testMerkleRoot, buildPartialMerkle, extractMatches,+ extractIndexedMatches,+ merkleBlockIndexedTxs,+ maxMerkleFlagBits, decodeMerkleFlags, encodeMerkleFlags, calcTreeHeight,@@ -36,14 +39,16 @@ where import Control.DeepSeq-import Control.Monad (when)+import Control.Monad (replicateM, when) import Data.Binary (Binary (..)) import Data.Bits+import qualified Data.ByteString as B import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Either (isRight) import Data.Hashable+import Data.Maybe (listToMaybe) import Data.Serialize (Serialize (..)) import qualified Data.Vector as V import Data.Word (Word32, Word8)@@ -77,13 +82,40 @@ } deriving (Eq, Show, Read, Generic, Hashable, NFData) +-- | The parse fails for a merkle block that no verifier accepts because of+-- its size alone: more hashes than transactions, or more flag bytes than+-- 'maxMerkleFlagBits' allows. Both are refused by @ExtractMatches@ of+-- @CPartialMerkleTree@ in Bitcoin Core and Bitcoin Cash Node, the first as+-- such and the second as unused flag bits. Checking them here, before the+-- hashes and the flags are read, bounds the memory that a peer can make the+-- parser use. The flag bytes are kept packed and unpacked on demand. instance Serial MerkleBlock where deserialize = do header <- deserialize ntx <- getWord32le- hashes <- getVarIntList deserialize- flagBytes <- getVarIntList getWord8- return $ MerkleBlock header ntx hashes (decodeMerkleFlags flagBytes)+ VarInt hashCount <- deserialize+ when (hashCount > maxListSize) $+ fail ("List count too large: " ++ show hashCount)+ when (hashCount > fromIntegral ntx) $+ fail $+ "Merkle block with more hashes than transactions: "+ ++ show hashCount+ ++ " hashes, "+ ++ show ntx+ ++ " transactions"+ hashes <- replicateM (fromIntegral hashCount) deserialize+ VarInt flagCount <- deserialize+ let maxBits = maxMerkleFlagBits (fromIntegral ntx) (fromIntegral hashCount)+ when (toInteger flagCount > (maxBits + 7) `div` 8) $+ fail $+ "Merkle block with more flag bytes than its tree can use: "+ ++ show flagCount+ ++ " bytes, at most "+ ++ show ((maxBits + 7) `div` 8)+ flagBytes <- getByteString (fromIntegral flagCount)+ -- The copy lets go of the message buffer. The list of bits is built when+ -- it is asked for.+ return $ MerkleBlock header ntx hashes (decodeMerkleFlags (B.unpack (B.copy flagBytes))) serialize (MerkleBlock h ntx hashes flags) = do serialize h@@ -99,6 +131,27 @@ put = serialize get = deserialize +-- | Largest number of flag bits that a valid partial Merkle tree can use,+-- for a block of the given number of transactions and a proof of the given+-- number of hashes. A proof spends one flag bit on each tree node it visits.+-- Every node it visits lies on the path from the root to one of its hashes,+-- which has at most the tree height plus one nodes, and it cannot visit more+-- nodes than the tree has.+maxMerkleFlagBits ::+ -- | number of transactions in the block+ Integer ->+ -- | number of hashes in the proof+ Integer ->+ Integer+maxMerkleFlagBits ntx hashCount+ | ntx <= 0 || hashCount <= 0 = 0+ | otherwise = min (hashCount * (height + 1)) nodes+ where+ widths = takeWhile (> 1) (iterate (\w -> (w + 1) `div` 2) ntx)+ -- one level per width above one, and the root+ height = toInteger (length widths)+ nodes = sum widths + 1+ -- | Unpack Merkle flags into 'FlagBits' structure. The least significant bit -- of each byte comes first. decodeMerkleFlags :: [Word8] -> FlagBits@@ -172,9 +225,9 @@ 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. Builds the whole--- tree, so use 'buildMerkleRoot' or 'buildPartialMerkle' for more than one--- node.+-- | Computes the hash of a specific node in a Merkle tree, or 'Nothing' when+-- the tree has no node at that height and position. Builds the whole tree,+-- so use 'buildMerkleRoot' or 'buildPartialMerkle' for more than one node. calcHash :: -- | height of the node Int ->@@ -183,10 +236,10 @@ -- | transaction hashes (leaf nodes) [TxHash] -> -- | hash of the node at the specified position- Hash256+ Maybe Hash256 calcHash height pos txs- | height < 0 || pos < 0 = error "calcHash: Invalid parameters"- | otherwise = (levels !! height) V.! pos+ | height < 0 || pos < 0 || null txs = Nothing+ | otherwise = listToMaybe (drop height levels) >>= (V.!? pos) where (levels, _) = merkleLevels (map (.get) txs) @@ -236,15 +289,15 @@ -- | 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.+-- of the node, the matching transactions below it with their positions in+-- the block, and the flags and hashes that are still unused. traverseAndExtract :: Int -> Int -> Int -> FlagBits -> PartialMerkleTree ->- Either String (Hash256, [TxHash], FlagBits, PartialMerkleTree)+ Either String (Hash256, [(Int, TxHash)], FlagBits, PartialMerkleTree) traverseAndExtract height pos ntx flags hashes = case flags of [] -> Left "extractMatches: ran out of flag bits"@@ -254,7 +307,7 @@ case hashes of [] -> Left "extractMatches: ran out of hashes" (h : hashes') ->- Right (h, [TxHash h | height == 0 && parentOfMatch], flags', hashes')+ Right (h, [(pos, TxHash h) | height == 0 && parentOfMatch], flags', hashes') | otherwise -> do (left, leftMatches, flags'', hashes'') <- traverseAndExtract (height - 1) (pos * 2) ntx flags' hashes@@ -270,7 +323,8 @@ -- | 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--- a call to 'buildPartialMerkle'.+-- a call to 'buildPartialMerkle'. See 'extractIndexedMatches' for what this+-- does not prove. extractMatches :: Network -> FlagBits ->@@ -279,7 +333,29 @@ Int -> -- | Merkle root and list of matching transaction hashes Either String (MerkleRoot, [TxHash])-extractMatches net flags hashes ntx+extractMatches net flags hashes ntx =+ fmap (map snd) <$> extractIndexedMatches net flags hashes ntx++-- | Like 'extractMatches', and gives the position of each matching+-- transaction in the block, as @ExtractMatches@ does in Bitcoin Core.+--+-- The number of transactions is a claim of whoever made the proof. Nothing+-- here can check it, and it sets the depth of the tree. A 64-byte+-- transaction hashes like an inner node of the tree, so a proof that claims+-- twice as many transactions can present the two halves of such a+-- transaction as two matches (CVE-2017-12842). A verifier that knows the+-- block compares the count with it, as Bitcoin Core does. One that does not+-- can ask for a proof of the coinbase transaction, which is always at+-- position 0, and require the same number of transactions in both proofs.+extractIndexedMatches ::+ Network ->+ FlagBits ->+ PartialMerkleTree ->+ -- | number of transaction at height 0 (leaf nodes)+ Int ->+ -- | Merkle root, and the matching transactions with their positions+ Either String (MerkleRoot, [(Int, TxHash)])+extractIndexedMatches net flags hashes ntx | ntx == 0 = Left "extractMatches: number of transactions can not be 0" | ntx > net.maxBlockSize `div` 60 =@@ -300,10 +376,12 @@ return (root, matches) -- | Helper function to split a list in chunks 'Int' length. Last chunk may be--- smaller.+-- smaller. A chunk length below one gives the whole list as one chunk. splitIn :: Int -> [a] -> [[a]] splitIn _ [] = []-splitIn c xs = xs1 : splitIn c xs2+splitIn c xs+ | c < 1 = [xs]+ | otherwise = xs1 : splitIn c xs2 where (xs1, xs2) = splitAt c xs @@ -312,14 +390,24 @@ boolsToWord8 [] = 0 boolsToWord8 xs = foldl setBit 0 (map snd $ filter fst $ zip xs [0 .. 7]) --- | Get matching transactions from Merkle block.+-- | Get matching transactions from Merkle block. The partial tree must+-- rebuild the Merkle root of the header that came in the same message. That+-- is all this checks: the caller has to validate the header itself, its proof+-- of work and its place in the chain, and the transaction count is a claim,+-- see 'extractIndexedMatches'. merkleBlockTxs :: Network -> MerkleBlock -> Either String [TxHash]-merkleBlockTxs net b = do- (root, ths) <- extractMatches net b.flags b.hashes (fromIntegral b.txn)+merkleBlockTxs net b = map snd <$> merkleBlockIndexedTxs net b++-- | Like 'merkleBlockTxs', with the position of each matching transaction+-- in the block.+merkleBlockIndexedTxs :: Network -> MerkleBlock -> Either String [(Int, TxHash)]+merkleBlockIndexedTxs net b = do+ (root, ths) <- extractIndexedMatches net b.flags b.hashes (fromIntegral b.txn) when (root /= b.header.merkle) $ Left "merkleBlockTxs: Merkle root incorrect" return ths --- | Check if Merkle block root is valid against the block header.+-- | Check if Merkle block root is valid against the block header. See+-- 'merkleBlockTxs' for what that does not prove. testMerkleRoot :: Network -> MerkleBlock -> Bool testMerkleRoot net = isRight . merkleBlockTxs net
src/Haskoin/Crypto/Keys/Common.hs view
@@ -112,10 +112,14 @@ 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.+-- 33 bytes starting with 0x02 or 0x03, or 65 bytes starting with 0x04. A key+-- pushed in a script, a witness or a PSBT record is the whole item, so use+-- this function, or 'unmarshal', for those. 'marshalGet' reads a key from the+-- start of a longer stream and leaves the rest.+--+-- A hybrid key, 65 bytes starting with 0x06 or 0x07, is refused. Consensus+-- takes that form, but the relay rules of Bitcoin Core do not, and no wallet+-- makes one. decodePubKey :: Ctx -> ByteString -> Either String PublicKey decodePubKey ctx = runGetS $ do k <- marshalGet ctx@@ -147,6 +151,12 @@ -- 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.+--+-- This protects a 'PrivateKey', not the 'SecKey' inside it. The 'Show'+-- instance of 'SecKey' comes from the secp256k1 binding and prints the 32+-- bytes in hex, so @show privateKey.key@ gives the secret away. A record of+-- your own that holds a 'SecKey' and derives 'Show' prints it too. Keep keys+-- in 'PrivateKey' or 'XPrvKey' where they may be shown. instance Show PrivateKey where showsPrec d p = showParen (d > 10) $@@ -176,33 +186,44 @@ 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.+-- | Build a secret key from its 32 big-endian bytes. Fails when there are not+-- 32 bytes, and when the value is zero or not below the curve order: a secret+-- key is a number from 1 to n-1. Every form of a private key in this library+-- is read through this function: WIF, mini keys, extended keys and the binary+-- form.+--+-- The checks are those of 'Crypto.Secp256k1.secKey', which this function+-- calls. Version 2.0.0 of the binding checks the range. Earlier versions only+-- checked the length, and this package requires 2.0.0 or later. decodeSecKey :: ByteString -> Maybe SecKey-decodeSecKey bs = do- guard (BS.length bs == 32)- let n = bsToInteger bs- guard (n >= 1 && n < curveOrder)- secKey bs+decodeSecKey = secKey -- | Tweak a private key. tweakSecKey :: Ctx -> SecKey -> Hash256 -> Maybe SecKey tweakSecKey ctx k = tweakAddSecKey ctx k <=< tweak . runPutS . serialize --- | Decode Casascius mini private keys (22 or 30 characters).+-- | Decode a Casascius mini private key, as the Bitcoin wiki describes the+-- format (<https://en.bitcoin.it/wiki/Mini_private_key_format>):+--+-- * 30 characters, or the 22 characters of the first series of coins+-- * every character from the Base58 alphabet, the first one the letter @S@+-- * SHA-256 of the key followed by @?@ starts with a zero byte+--+-- The private key is SHA-256 of the mini key, and its public key is+-- uncompressed. A string that breaks one of these rules is not a mini key,+-- even when its check byte happens to be zero, which one string in 256 has. fromMiniKey :: ByteString -> Maybe PrivateKey fromMiniKey bs = do- guard checkShortKey+ guard (BS.length bs `elem` [22, 30])+ guard (BS.head bs == letterS)+ guard (BS.all (`BS.elem` base58Alphabet) bs)+ guard (BS.head checkHash == 0x00) 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+ letterS = 0x53+ base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -- | 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
src/Haskoin/Crypto/Keys/Extended.hs view
@@ -10,6 +10,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE NoFieldSelectors #-} -- |@@ -68,7 +69,7 @@ cycleIndex, -- ** Derivation Paths- DerivPathI (..),+ DerivPathI (Deriv, (:|), (:/)), AnyDeriv, HardDeriv, SoftDeriv,@@ -80,6 +81,8 @@ Bip32PathIndex (..), derivePath, derivePubPath,+ tryDerivePath,+ tryDerivePubPath, toHard, toSoft, toGeneric,@@ -106,7 +109,7 @@ import Control.Applicative import Control.DeepSeq import Control.Exception (Exception, throw)-import Control.Monad (guard, mzero, unless, when, (<=<))+import Control.Monad (foldM, guard, mzero, unless, when, (<=<)) import Crypto.Secp256k1 import Data.Aeson as Aeson ( FromJSON,@@ -119,6 +122,7 @@ import Data.Aeson.Encoding (Encoding, string, text) import Data.Aeson.Types (Parser) import Data.Binary (Binary (get, put))+import Data.Bifunctor (first) import Data.Bits (clearBit, setBit, testBit) import Data.ByteString (ByteString) import Data.ByteString qualified as B@@ -205,6 +209,9 @@ -- 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.+--+-- The @key@ field is a 'SecKey', whose own 'Show' instance prints the secret+-- in hex. Do not show that field by itself. instance Show XPrvKey where showsPrec d k = showParen (d > 10) $@@ -270,9 +277,7 @@ p <- deserialize i <- getWord32be checkMasterFields d p i- XPubKey d p i- <$> deserialize- <*> ((\PublicKey {point} -> point) <$> marshalGet ctx)+ XPubKey d p i <$> deserialize <*> getCompressedPubKey ctx marshalPut (net, ctx) k = do putWord32be net.xPubPrefix@@ -293,6 +298,18 @@ marshalEncoding (net, ctx) = text . xPubExport net ctx +-- | Read the public key of an extended key. BIP32 serializes it in 33 bytes,+-- the compressed form. The 65-byte form is refused, so that an extended key+-- has one binary form of 78 bytes.+getCompressedPubKey :: (MonadGet m) => Ctx -> m PubKey+getCompressedPubKey ctx = do+ bs <- getByteString 33+ unless (B.head bs `elem` [0x02, 0x03]) $+ fail "Extended key must hold a compressed public key"+ case importPubKey ctx bs of+ Nothing -> fail "Extended key holds an invalid public key"+ Just k -> return k+ -- | 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.@@ -301,13 +318,24 @@ 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@). Throws 'DerivationException'--- in the vanishingly unlikely case that the seed gives a key outside the curve--- order, which BIP32 declares invalid.+-- | Build a BIP32 compatible extended private key from a seed. This will+-- produce a root node (@depth=0@ and @parent=0@).+--+-- The seed must be 16 to 64 bytes long: BIP32 takes a seed "between 128 and+-- 512 bits", and Bitcoin Core refuses any other length. A shorter seed, such+-- as the empty string left by a failed read of entropy, would give a master+-- key that anyone can compute.+--+-- Throws 'DerivationException' for a seed of another length, and 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+makeXPrvKey bs+ | B.length bs < 16 || B.length bs > 64 =+ throw $+ DerivationException+ "Invalid seed: its length must be between 16 and 64 bytes"+ | otherwise = XPrvKey 0 (Fingerprint 0) 0 c k where (p, c) = split512 $ hmac512 "Bitcoin seed" bs k = fromMaybe err (decodeSecKey (runPutS (serialize p)))@@ -336,16 +364,21 @@ KeyIndex -> -- | extended child private key XPrvKey-prvSubKey ctx xkey child- | child < 0x80000000 =- XPrvKey (nextDepth xkey.depth) (xPrvFP ctx xkey) child c k- | otherwise = throw hardIndexException+prvSubKey ctx xkey child = orThrow (tryPrvSubKey ctx xkey child)++-- | 'prvSubKey' with its failures as an error value.+tryPrvSubKey :: Ctx -> XPrvKey -> KeyIndex -> Either String XPrvKey+tryPrvSubKey ctx xkey child = do+ unless (child < 0x80000000) $ Left hardIndexError+ depth <- nextDepth xkey.depth+ k <-+ maybeToEither "Invalid prvSubKey derivation" $+ tweakSecKey ctx xkey.key a+ return $ XPrvKey depth (xPrvFP ctx xkey) child c k where pK = (deriveXPubKey ctx xkey).key m = B.append (exportPubKey ctx True pK) (runPutS (serialize child)) (a, c) = split512 $ hmac512 (runPutS $ serialize xkey.chain) m- k = fromMaybe err $ tweakSecKey ctx xkey.key a- err = throw $ DerivationException "Invalid prvSubKey derivation" -- | Compute a public, soft child key derivation. Given a parent key /M/ -- and a derivation index /i/, this function will compute /M\/i/.@@ -357,15 +390,20 @@ KeyIndex -> -- | extended child public key XPubKey-pubSubKey ctx xKey child- | child < 0x80000000 =- XPubKey (nextDepth xKey.depth) (xPubFP ctx xKey) child c pK- | otherwise = throw hardIndexException+pubSubKey ctx xKey child = orThrow (tryPubSubKey ctx xKey child)++-- | 'pubSubKey' with its failures as an error value.+tryPubSubKey :: Ctx -> XPubKey -> KeyIndex -> Either String XPubKey+tryPubSubKey ctx xKey child = do+ unless (child < 0x80000000) $ Left hardIndexError+ depth <- nextDepth xKey.depth+ pK <-+ maybeToEither "Invalid pubSubKey derivation" $+ tweakPubKey ctx xKey.key a+ return $ XPubKey depth (xPubFP ctx xKey) child c pK where m = B.append (exportPubKey ctx True xKey.key) (runPutS $ serialize child) (a, c) = split512 $ hmac512 (runPutS $ serialize xKey.chain) m- pK = fromMaybe err $ tweakPubKey ctx xKey.key a- err = throw $ DerivationException "Invalid pubSubKey derivation" -- | Compute a hard child key derivation. Hard derivations can only be computed -- for private keys. Hard derivations do not allow the parent public key to@@ -381,31 +419,45 @@ KeyIndex -> -- | extended child private key XPrvKey-hardSubKey ctx xkey child- | child < 0x80000000 =- XPrvKey (nextDepth xkey.depth) (xPrvFP ctx xkey) i c k- | otherwise = throw hardIndexException+hardSubKey ctx xkey child = orThrow (tryHardSubKey ctx xkey child)++-- | 'hardSubKey' with its failures as an error value.+tryHardSubKey :: Ctx -> XPrvKey -> KeyIndex -> Either String XPrvKey+tryHardSubKey ctx xkey child = do+ unless (child < 0x80000000) $ Left hardIndexError+ depth <- nextDepth xkey.depth+ k <-+ maybeToEither "Invalid hardSubKey derivation" $+ tweakSecKey ctx xkey.key a+ return $ XPrvKey depth (xPrvFP ctx xkey) i c k where i = setBit child 31 m = B.append (bsPadPrvKey xkey.key) (runPutS $ serialize i) (a, c) = split512 $ hmac512 (runPutS $ serialize xkey.chain) m- 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+-- | Turn the failure of a derivation into a 'DerivationException'. Every+-- failure of a derivation function that does not return 'Either' throws that+-- exception, so that a caller can catch them all at once.+orThrow :: Either String a -> a+orThrow = either (throw . DerivationException) id++-- | The error 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"+-- sets the bit itself.+hardIndexError :: String+hardIndexError = "Invalid child derivation index" +-- | The error for a derivation below depth 255.+depthError :: String+depthError = "Maximum derivation depth exceeded"+ -- | 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+-- depth 255 can have no children. Bitcoin Core refuses to derive one+-- (@CExtKey::Derive@ and @CExtPubKey::Derive@ return false), and so does this+-- library.+nextDepth :: Word8 -> Either String Word8+nextDepth 255 = Left depthError+nextDepth d = Right (d + 1) -- | Returns true if the extended private key was derived through a hard -- derivation.@@ -479,7 +531,7 @@ -- always exists. xPubCompatWitnessAddr :: Ctx -> XPubKey -> Address xPubCompatWitnessAddr ctx xkey =- payToScriptAddress ctx (PayWitnessPKHash (xPubKeyHash ctx xkey))+ p2shP2wpkhAddr (xPubKeyHash ctx xkey) -- | HASH160 of the compressed public key of an extended public key. xPubKeyHash :: Ctx -> XPubKey -> Hash160@@ -597,30 +649,45 @@ where f i = let (a, key) = deriveCompatWitnessAddr ctx k i in (a, key, i) --- | Derive a multisig address from a list of public keys, the number of--- required signatures /m/ and a derivation index. The derivation type is a--- public, soft derivation.+-- | Derive a pay-to-script-hash multisig address from a list of public keys,+-- the number of required signatures /m/ and a derivation index. The+-- derivation type is a public, soft derivation, and the keys of the redeem+-- script are sorted as BIP 67 defines.+--+-- Fails when /m/ is not between 1 and the number of keys, and when there are+-- more than 15 keys. The redeem script of 16 compressed keys is 547 bytes+-- (3 + 16 × 34), and a pay-to-script-hash redeem script can have at most 520+-- ('maxRedeemScriptSize'), so coins sent to such an address could never be+-- spent. For 16 to 20 keys, build a pay-to-witness-script-hash address from+-- the redeem script with 'payToWitnessScriptAddress'. deriveMSAddr ::- Ctx -> [XPubKey] -> Int -> KeyIndex -> (Address, RedeemScript)-deriveMSAddr ctx keys m i = (payToScriptAddress ctx rdm, rdm)+ Ctx -> [XPubKey] -> Int -> KeyIndex -> Either String (Address, RedeemScript)+deriveMSAddr ctx keys m i = do+ addr <- payToScriptAddress ctx rdm+ return (addr, rdm) where rdm = sortMulSig ctx $ PayMulSig k m k = map (wrapPubKey True . (.key) . flip (pubSubKey ctx) i) keys -- | Cyclic list of all multisig addresses derived from a list of public keys, -- a number of required signatures /m/ and starting from an offset index. The--- derivation type is a public, soft derivation.+-- derivation type is a public, soft derivation. Fails as 'deriveMSAddr' does. deriveMSAddrs :: Ctx -> [XPubKey] -> Int -> KeyIndex ->- [(Address, RedeemScript, KeyIndex)]-deriveMSAddrs ctx keys m = map f . cycleIndex- where- f i =- let (a, rdm) = deriveMSAddr ctx keys m i- in (a, rdm, i)+ Either String [(Address, RedeemScript, KeyIndex)]+deriveMSAddrs ctx keys m start = do+ -- Whether an address can be made depends on the number of keys and on m,+ -- not on the index. One check therefore covers the whole list, which stays+ -- lazy and endless.+ _ <- deriveMSAddr ctx keys m start+ return+ [ (a, rdm, i)+ | i <- cycleIndex start,+ Right (a, rdm) <- [deriveMSAddr ctx keys m i]+ ] -- | Helper function to go through derivation indices. Throws -- 'DerivationException' for an index with the hard bit set.@@ -680,11 +747,36 @@ -- > Deriv :/ 0 :/ 1 :/ 2 :: SoftPath -- > Deriv :| 0 :| 1 :| 2 :: HardPath -- > Deriv :| 0 :/ 1 :/ 2 :: DerivPath+--+-- The index of a step is below 2^31, for a hardened step as well: '(:|)'+-- stands for the hard bit. Building a step with a larger index throws+-- 'DerivationException'. A path can therefore not hold an index that its+-- text and binary forms cannot write. data DerivPathI t where- (:|) :: (HardOrAny t) => !(DerivPathI t) -> !KeyIndex -> DerivPathI t- (:/) :: (AnyOrSoft t) => !(DerivPathI t) -> !KeyIndex -> DerivPathI t+ HardStep :: (HardOrAny t) => !(DerivPathI t) -> !KeyIndex -> DerivPathI t+ SoftStep :: (AnyOrSoft t) => !(DerivPathI t) -> !KeyIndex -> DerivPathI t Deriv :: DerivPathI t +-- | A hardened step. The index is given without the hard bit.+pattern (:|) :: () => (HardOrAny t) => DerivPathI t -> KeyIndex -> DerivPathI t+pattern next :| i <- HardStep next i+ where+ next :| i = HardStep next (checkedIndex i)++-- | A soft step.+pattern (:/) :: () => (AnyOrSoft t) => DerivPathI t -> KeyIndex -> DerivPathI t+pattern next :/ i <- SoftStep next i+ where+ next :/ i = SoftStep next (checkedIndex i)++{-# COMPLETE (:|), (:/), Deriv #-}++-- | The index of a path step, which must be below 2^31.+checkedIndex :: KeyIndex -> KeyIndex+checkedIndex i+ | i < 0x80000000 = i+ | otherwise = throw (DerivationException hardIndexError)+ instance NFData (DerivPathI t) where rnf (a :| b) = rnf a `seq` rnf b rnf (a :/ b) = rnf a `seq` rnf b@@ -821,25 +913,58 @@ next :| i -> go (f . (:| i)) $ toGeneric next _ -> f --- | Derive a private key from a derivation path+-- | Derive a private key from a derivation path. Throws+-- 'DerivationException' when the path cannot be followed, for example when it+-- leads below depth 255. Use 'tryDerivePath' for a path that comes from+-- outside, such as the path of a PSBT record. derivePath :: Ctx -> DerivPathI t -> XPrvKey -> XPrvKey-derivePath ctx = go id- where- -- Build the full derivation function starting from the end- go f p = case p of- next :| i -> go (f . flip (hardSubKey ctx) i) next- next :/ i -> go (f . flip (prvSubKey ctx) i) next- _ -> f+derivePath ctx path = orThrow . tryDerivePath ctx path --- | Derive a public key from a soft derivation path+-- | Derive a public key from a soft derivation path. Throws+-- 'DerivationException' when the path cannot be followed. Use+-- 'tryDerivePubPath' for a path that comes from outside. derivePubPath :: Ctx -> SoftPath -> XPubKey -> XPubKey-derivePubPath ctx = go id+derivePubPath ctx path = orThrow . tryDerivePubPath ctx path++-- | Derive a private key from a derivation path, with an error value when the+-- path cannot be followed. BIP32 stores the depth of a key in one byte, so a+-- path that leads below depth 255 has no key. That is checked before any key+-- is derived, so a path that is far too long costs nothing.+tryDerivePath :: Ctx -> DerivPathI t -> XPrvKey -> Either String XPrvKey+tryDerivePath ctx path key = do+ checkPathDepth key.depth steps+ foldM step key steps where- -- Build the full derivation function starting from the end- go f p = case p of- next :/ i -> go (f . flip (pubSubKey ctx) i) next- _ -> f+ steps = pathSteps path+ step k (hard, i)+ | hard = tryHardSubKey ctx k i+ | otherwise = tryPrvSubKey ctx k i +-- | Derive a public key from a soft derivation path, with an error value when+-- the path cannot be followed. See 'tryDerivePath'.+tryDerivePubPath :: Ctx -> SoftPath -> XPubKey -> Either String XPubKey+tryDerivePubPath ctx path key = do+ checkPathDepth key.depth steps+ foldM (tryPubSubKey ctx) key (map snd steps)+ where+ steps = pathSteps path++-- | The steps of a path from the key down. Each step says whether it is+-- hardened, and gives its index without the hard bit.+pathSteps :: DerivPathI t -> [(Bool, KeyIndex)]+pathSteps =+ reverse . go+ where+ go (next :| i) = (True, i) : go next+ go (next :/ i) = (False, i) : go next+ go Deriv = []++-- | Fail when a key at this depth has no descendant that many steps below.+checkPathDepth :: Word8 -> [a] -> Either String ()+checkPathDepth depth steps+ | length steps > 255 - fromIntegral depth = Left depthError+ | otherwise = Right ()+ instance Show DerivPath where showsPrec d p = showParen (d > 10) $@@ -978,8 +1103,11 @@ -- | 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.+-- index. BIP32 itself writes a hardened index with a subscript H, as in+-- @m\/0H\/1@. Bitcoin Core and the descriptors of BIP380 take @'@ and @h@+-- only, so write one of those two for text that other software reads.+-- 'pathToStr' writes @'@. 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 span isDigit segment of@@ -1057,33 +1185,24 @@ -- | 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 -- path starts with /M/, a public key will be returned. Private derivations on a--- public key, and public derivations with a hard segment, return an error--- value.+-- public key, public derivations with a hard segment, and a path that leads+-- below depth 255 return an error value. The function does not throw. applyPath :: Ctx -> ParsedPath -> XKey -> Either String XKey applyPath ctx path key =- case (path, key) of- (ParsedPrv _, XPrv k n) -> return $ XPrv (derivPrvF k) n- (ParsedPrv _, XPub {}) -> Left "applyPath: Invalid public key"- (ParsedPub _, XPrv k n) -> return $ XPub (deriveXPubKey ctx (derivPrvF k)) n- (ParsedPub _, XPub k n) -> derivPubFE >>= \f -> return $ XPub (f k) n- -- For empty parsed paths, we take a hint from the provided key- (ParsedEmpty _, XPrv k n) -> return $ XPrv (derivPrvF k) n- (ParsedEmpty _, XPub k n) -> derivPubFE >>= \f -> return $ XPub (f k) n+ first ("applyPath: " <>) $+ case (path, key) of+ (ParsedPrv _, XPrv k n) -> (`XPrv` n) <$> derivePrv k+ (ParsedPrv _, XPub {}) -> Left "Invalid public key"+ (ParsedPub _, XPrv k n) -> (`XPub` n) . deriveXPubKey ctx <$> derivePrv k+ (ParsedPub _, XPub k n) -> (`XPub` n) <$> derivePub k+ -- For empty parsed paths, we take a hint from the provided key+ (ParsedEmpty _, XPrv k n) -> (`XPrv` n) <$> derivePrv k+ (ParsedEmpty _, XPub k n) -> (`XPub` n) <$> derivePub k where- derivPrvF = goPrv id path.get- derivPubFE = goPubE id path.get- -- Build the full private derivation function starting from the end- goPrv f p =- case p of- next :| i -> goPrv (f . flip (hardSubKey ctx) i) next- next :/ i -> goPrv (f . flip (prvSubKey ctx) i) next- Deriv -> f- -- Build the full public derivation function starting from the end- goPubE f p =- case p of- next :/ i -> goPubE (f . flip (pubSubKey ctx) i) next- Deriv -> Right f- _ -> Left "applyPath: Invalid hard derivation"+ derivePrv = tryDerivePath ctx path.get+ derivePub k = do+ soft <- maybeToEither "Invalid hard derivation" (toSoft path.get)+ tryDerivePubPath ctx soft k {- Helpers for derivation paths and addresses -} @@ -1098,27 +1217,27 @@ derivePathAddrs ctx key path = deriveAddrs ctx (derivePubPath ctx path key) -- | Derive a multisig address from a given parent path. The number of required--- signatures (m in m of n) is also needed.+-- signatures (m in m of n) is also needed. Fails as 'deriveMSAddr' does. derivePathMSAddr :: Ctx -> [XPubKey] -> SoftPath -> Int -> KeyIndex ->- (Address, RedeemScript)+ Either String (Address, RedeemScript) derivePathMSAddr ctx keys path = deriveMSAddr ctx $ map (derivePubPath ctx path) keys -- | Cyclic list of all multisig addresses derived from a given parent path and -- starting from the given offset index. The number of required signatures--- (m in m of n) is also needed.+-- (m in m of n) is also needed. Fails as 'deriveMSAddr' does. derivePathMSAddrs :: Ctx -> [XPubKey] -> SoftPath -> Int -> KeyIndex ->- [(Address, RedeemScript, KeyIndex)]+ Either String [(Address, RedeemScript, KeyIndex)] derivePathMSAddrs ctx keys path = deriveMSAddrs ctx $ map (derivePubPath ctx path) keys
src/Haskoin/Crypto/Keys/Mnemonic.hs view
@@ -11,6 +11,11 @@ -- Portability : POSIX -- -- Mnemonic keys (BIP-39). Only English dictionary.+--+-- The sentence and the passphrase may come from an untrusted source, so the+-- work done on them is bounded. A word longer than any word of the list is+-- never normalised, and a passphrase must be in the Stream-Safe Text Format+-- of Unicode. No error text holds a word of the sentence. module Haskoin.Crypto.Keys.Mnemonic ( -- * Mnemonic Sentences Entropy,@@ -32,10 +37,10 @@ import Data.ByteArray qualified as BA import Data.ByteString (ByteString) import Data.ByteString qualified as B+import Data.Char (isAscii) import Data.List import Data.Map.Strict qualified as M import Data.Maybe-import Data.String.Conversions (cs) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as E@@ -43,18 +48,29 @@ import Data.Vector (Vector, (!)) import Data.Vector qualified as V import Haskoin.Util+import Unicode.Char qualified as UC -- | Random data used to create a mnemonic sentence. Use a good entropy source. -- You will get your coins stolen if you don't. You have been warned.+--+-- This is a plain 'ByteString'. Its 'Show' instance prints the bytes, so+-- keep it out of logs and error messages. type Entropy = ByteString -- | Human-readable mnemonic sentence.+--+-- This is a plain 'Text', and so is 'Passphrase'. The compiler does not+-- tell the two apart, so mind the order of the arguments of+-- 'mnemonicToSeed': the passphrase comes first. The 'Show' instance prints+-- the sentence in full, so keep it out of logs and error messages. type Mnemonic = Text --- | Optional passphrase for mnemnoic sentence.+-- | Optional passphrase for mnemnoic sentence. It may hold any text that+-- is in the Stream-Safe Text Format of Unicode, see 'mnemonicToSeed'. type Passphrase = Text --- | Seed for a private key from a mnemonic sentence.+-- | Seed for a private key from a mnemonic sentence. A plain 'ByteString',+-- which 'Show' prints in full. type Seed = ByteString -- | Mnemonic key checksum.@@ -76,6 +92,20 @@ validWordCounts :: [Int] validWordCounts = [12, 15, 18, 21, 24] +-- | The most letters of a word of the English list. NFKD never makes a+-- text shorter, so a word of the sentence with more characters than this+-- cannot be a word of the list, whatever its normal form is.+maxWordLength :: Int+maxWordLength = 8++-- | The most combining characters a passphrase may hold in a row once it+-- is normalised: the limit of the Stream-Safe Text Format of Unicode,+-- definition UAX15-D3 of Unicode Standard Annex 15. The annex chose 30 as+-- "significantly beyond what is required for any linguistic or technical+-- usage".+maxCombiningRun :: Int+maxCombiningRun = 30+ -- | 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@@ -97,8 +127,15 @@ -- '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.+-- Each word is NFKD-normalised before it is looked up, as BIP39 requires.+-- Only sentences of 12, 15, 18, 21 or 24 words are accepted. The words may+-- be separated by any amount of white space.+--+-- The sentence can come from an untrusted source, so the work is linear in+-- its length: the words are counted first, and a word longer than any word+-- of the list is refused without being normalised. An error names the+-- position of a wrong word, counted from 1, and never the word itself or a+-- checksum value, since the sentence is a secret and errors end up in logs. fromMnemonic :: Mnemonic -> Either String Entropy fromMnemonic ms = do when (T.null ms) $@@ -111,14 +148,15 @@ ms_cs_num = numCS cs_len ms_cs ent_cs_num = numCS cs_len $ calcCS cs_len ms_ent when (ent_cs_num /= ms_cs_num) $- Left $- "fromMnemonic: checksum failed: " ++ sh ent_cs_num ms_cs_num+ Left "fromMnemonic: checksum failed" return ms_ent where- ms_words = T.words (normalize NFKD ms)+ -- The sentence is split before anything is normalised. White space+ -- stays white space under NFKD and nothing is reordered across it, so+ -- the words are those of the normalised sentence.+ ms_words = T.words 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 -- | Compute 'Checksum'. calcCS :: Int -> Entropy -> Checksum@@ -132,36 +170,103 @@ 8 -> id 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. Both inputs are--- NFKD-normalised first, as BIP39 requires.-anyToSeed :: Passphrase -> Mnemonic -> Seed-anyToSeed pf ms =+-- | The 512-bit 'Seed' of a sentence and a passphrase that are both in+-- NFKD already: PBKDF2-HMAC-SHA512 of the sentence, salted with "mnemonic"+-- followed by the passphrase, 2048 rounds.+normalizedToSeed :: Passphrase -> Mnemonic -> Seed+normalizedToSeed pf ms = fastPBKDF2_SHA512 pbkdfParams- (E.encodeUtf8 (normalize NFKD ms))- ("mnemonic" `mappend` E.encodeUtf8 (normalize NFKD pf))+ (E.encodeUtf8 ms)+ ("mnemonic" `mappend` E.encodeUtf8 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. 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.+--+-- The passphrase can be of any length. It is refused when its normal form+-- would hold more than 30 combining characters in a row, which no real+-- text does. That is the limit of the Stream-Safe Text Format of Unicode+-- (Unicode Standard Annex 15, UAX15-D3). Without it the time to normalise+-- such a run grows with the square of its length, so that 32 KB of text+-- from an untrusted source took half a minute. mnemonicToSeed :: Passphrase -> Mnemonic -> Either String Seed mnemonicToSeed pf ms = do ent <- fromMnemonic ms+ -- The sentence is written again from its entropy, so it is in lower-case+ -- ASCII with single spaces, which is its NFKD form mnm <- toMnemonic ent- return $ anyToSeed pf mnm+ pf_nfkd <- normalizePassphrase pf+ return $ normalizedToSeed pf_nfkd mnm --- | Get indices of words in word list.+-- | The NFKD form of a passphrase, or an error when the passphrase is not+-- in the Stream-Safe Text Format.+normalizePassphrase :: Passphrase -> Either String Passphrase+normalizePassphrase pf+ -- NFKD leaves ASCII as it is+ | T.all isAscii pf = Right pf+ | exceedsCombiningRun maxCombiningRun pf =+ Left $+ "mnemonicToSeed: the passphrase has more than "+ ++ show maxCombiningRun+ ++ " combining characters in a row"+ | otherwise = Right (normalize NFKD pf)++-- | Whether the NFKD form of a text holds more combining characters in a+-- row than the limit. The text is not normalised as a whole to find out,+-- since that is the costly step. Each character is decomposed on its own:+-- normalising only reorders the combining characters inside a run, so the+-- runs of the decomposed characters are as long as those of the normal+-- form. A combining character is what the normaliser treats as one: a+-- character with a canonical combining class other than zero.+exceedsCombiningRun :: Int -> Text -> Bool+exceedsCombiningRun limit = go 0 . T.unpack+ where+ go :: Int -> String -> Bool+ go _ [] = False+ go run (c : rest)+ -- An ASCII character is its own decomposition and ends a run+ | isAscii c = go 0 rest+ | otherwise = case extend run (decomposition c) of+ Nothing -> True+ Just run' -> go run' rest+ -- The length of the run at the end of the characters given, when the+ -- run before them had the length given. Nothing when a run passes the+ -- limit.+ extend :: Int -> String -> Maybe Int+ extend run [] = Just run+ extend run (x : xs)+ | not (UC.isCombining x) = extend 0 xs+ | run >= limit = Nothing+ | otherwise = extend (run + 1) xs+ decomposition c = T.unpack (normalize NFKD (T.singleton c))++-- | Get indices of words in word list. An error names the positions of the+-- words that are not in the list, counted from 1. getIndices :: [Text] -> Either String [Int] getIndices ws- | null n = return $ catMaybes i- | otherwise = Left $ "fromMnemonic: words not in the list: " ++ cs w+ | null missing = return $ catMaybes found+ | otherwise = Left $ "fromMnemonic: " ++ wordsAt missing ++ " not in the list" where- i = map (`M.lookup` wordListMap) ws- n = elemIndices Nothing i- w = T.unwords $ map (ws !!) n+ found = map wordIndex ws+ missing = [n | (n, Nothing) <- zip [1 :: Int ..] found]++-- | The index of a word of the sentence in the word list, looked up in its+-- NFKD form. A word longer than any word of the list is not normalised.+wordIndex :: Text -> Maybe Int+wordIndex w+ | T.compareLength w maxWordLength == GT = Nothing+ | otherwise = M.lookup (normalize NFKD w) wordListMap++-- | The start of a sentence about the words at the positions given: "word+-- 3 is", "words 3 and 7 are", "words 1, 3 and 7 are".+wordsAt :: [Int] -> String+wordsAt positions = case reverse (map show positions) of+ [] -> "no word is"+ [one] -> "word " ++ one ++ " is"+ final : before -> "words " ++ intercalate ", " (reverse before) ++ " and " ++ final ++ " are" -- | Turn a list of 11-bit numbers into a 'ByteString' indicesToBS :: [Int] -> Either String ByteString
+ src/Haskoin/Crypto/Keys/Slip39.hs view
@@ -0,0 +1,1898 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-}++-- |+-- Module : Haskoin.Crypto.Keys.Slip39+-- Copyright : No rights reserved+-- License : MIT+-- Maintainer : jprupp@protonmail.ch+-- Stability : experimental+-- Portability : POSIX+--+-- Shamir's secret-sharing for mnemonic codes, SLIP-0039. A master secret is+-- encrypted with a passphrase and split into shares, of which a chosen+-- number rebuild it. Each share is a mnemonic of the SLIP-0039 word list,+-- with a checksum that catches any three wrong words. The split has two+-- levels: the encrypted master secret is shared among groups, and each+-- group's share among its members. A plain T-of-N split is one group with+-- a member threshold of T and N members.+--+-- The specification is at+-- <https://github.com/satoshilabs/slips/blob/master/slip-0039.md>. This+-- module follows it section by section: the field GF(256), the Lagrange+-- interpolation, the splitting and recovery of a secret with its digest,+-- the format of a share mnemonic, the RS1024 checksum, the encryption of+-- the master secret with the passphrase, and the generation and combination+-- of the shares.+--+-- The master secret of a BIP-0032 wallet is its seed, the bytes fed to+-- HMAC-SHA512 to derive the master key. A SLIP-0039 share is not a BIP-0039+-- mnemonic and cannot be converted into one.+--+-- A share is a secret. The 'Show' instances of this module leave the share+-- values out, and an error names the position of a wrong word, never the+-- word. A mnemonic can come from an untrusted source, so its length is+-- checked before anything else: a share has at most 1023 words.+--+-- The field arithmetic looks bytes of the secret up in tables, as the+-- reference implementation does, so its timing depends on them. Hardware+-- wallets use constant-time code. Do not split or combine secrets where an+-- attacker can time the operation closely.+module Haskoin.Crypto.Keys.Slip39+ ( -- * Shares+ Share (..),+ encodeShare,+ decodeShare,++ -- * Generating shares+ Scheme (..),+ generateMnemonics,+ generateShares,+ generateSharesFrom,+ generateSharesWith,+ SplitRandom (..),+ splitRandomSizes,++ -- * Combining shares+ combineMnemonics,+ combineShares,+ EncryptedMasterSecret (..),++ -- * The passphrase+ encryptMasterSecret,+ decryptMasterSecret,+ decryptMasterSecretBytes,++ -- * Shamir's secret sharing+ splitSecret,+ recoverSecret,+ interpolate,+ secretIndex,+ digestIndex,++ -- * The field GF(256)+ gfMul,+ gfInv,+ gfDiv,++ -- * The checksum+ rs1024Polymod,+ rs1024Checksum,+ rs1024Verify,+ customizationString,++ -- * The word list+ slip39WordList,+ slip39WordListMap,+ mnemonicToIndices,+ indicesToMnemonic,+ )+where++import Control.Monad (foldM, forM, forM_, replicateM, unless, when)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Except (ExceptT (..), runExceptT, throwE)+import Crypto.Hash (SHA256)+import Crypto.KDF.PBKDF2 (Parameters (..), fastPBKDF2_SHA256)+import Crypto.MAC.HMAC (HMAC, hmac)+import Data.Array.Unboxed (UArray, listArray, (!))+import Data.Bits (shiftL, shiftR, testBit, xor, (.&.), (.|.))+import Data.ByteArray qualified as BA+import Data.ByteString (ByteString)+import Data.ByteString qualified as B+import Data.Char (isAscii, ord)+import Data.List (nub, transpose)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as M+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding qualified as E+import Data.Vector (Vector)+import Data.Vector qualified as V+import Data.Word (Word8)+import System.Entropy (getEntropy)++{- Shares -}++-- | A share, as its mnemonic carries it. The identifier is a random 15-bit+-- value, the same on every share of a split. The iteration exponent sets+-- the PBKDF2 iterations of the passphrase to 10000 times 2 to its power.+-- The indices are the x values of the share at each level, 0 to 15, and the+-- thresholds and the group count are the actual numbers, 1 to 16, not the+-- values the mnemonic encodes, which are one less.+data Share = Share+ { identifier :: !Int,+ extendable :: !Bool,+ iterationExponent :: !Int,+ groupIndex :: !Int,+ groupThreshold :: !Int,+ groupCount :: !Int,+ memberIndex :: !Int,+ memberThreshold :: !Int,+ -- | The share value: f(x) of each byte of the secret shared+ value :: !ByteString+ }+ deriving (Eq, Ord)++-- | The value of a share is secret, so it is not shown.+instance Show Share where+ showsPrec d s =+ showParen (d > 10) $+ showString "Share {identifier = "+ . shows s.identifier+ . showString ", extendable = "+ . shows s.extendable+ . showString ", iterationExponent = "+ . shows s.iterationExponent+ . showString ", groupIndex = "+ . shows s.groupIndex+ . showString ", groupThreshold = "+ . shows s.groupThreshold+ . showString ", groupCount = "+ . shows s.groupCount+ . showString ", memberIndex = "+ . shows s.memberIndex+ . showString ", memberThreshold = "+ . shows s.memberThreshold+ . showString ", value = <redacted>}"++-- | The master secret encrypted with its passphrase, with what its+-- decryption needs besides the passphrase. This is what the shares share.+data EncryptedMasterSecret = EncryptedMasterSecret+ { identifier :: !Int,+ extendable :: !Bool,+ iterationExponent :: !Int,+ ciphertext :: !ByteString+ }+ deriving (Eq)++-- | The ciphertext is secret, so it is not shown.+instance Show EncryptedMasterSecret where+ showsPrec d e =+ showParen (d > 10) $+ showString "EncryptedMasterSecret {identifier = "+ . shows e.identifier+ . showString ", extendable = "+ . shows e.extendable+ . showString ", iterationExponent = "+ . shows e.iterationExponent+ . showString ", ciphertext = <redacted>}"++-- | How a master secret is split: how many groups are needed to rebuild+-- it, and, for each group, how many of its members are needed and how many+-- members it has. A plain T-of-N split is @Scheme 1 [(T, N)]@.+data Scheme = Scheme+ { groupThreshold :: !Int,+ groups :: ![(Int, Int)]+ }+ deriving (Eq, Show)++{- Constants of the format -}++-- | The index at which the shared secret is stored, f(255).+secretIndex :: Word8+secretIndex = 255++-- | The index at which the digest of the shared secret is stored, f(254).+digestIndex :: Word8+digestIndex = 254++-- | The bytes of the digest at the head of the digest share.+digestBytes :: Int+digestBytes = 4++-- | The most shares a secret can be split into, and the most groups: the+-- indices have four bits.+maxShares :: Int+maxShares = 16++-- | The fewest bytes of a master secret: 128 bits.+minSecretBytes :: Int+minSecretBytes = 16++-- | The words of a mnemonic that are not share value: four of header and+-- three of checksum.+metadataWords :: Int+metadataWords = 7++-- | The header words of a mnemonic: the identifier, the flag, the exponent,+-- the indices, the thresholds and the group count, 40 bits in all.+headerWords :: Int+headerWords = 4++-- | The fewest words of a mnemonic: the metadata and 13 words of value for+-- a secret of 128 bits.+minMnemonicWords :: Int+minMnemonicWords = 20++-- | The most words of a mnemonic. The checksum is a Reed-Solomon code over+-- GF(1024), whose code words have at most 1023 symbols, the "up to a+-- thousand words" of the specification. Beyond that length the checksum no+-- longer promises to catch any three wrong words.+maxMnemonicWords :: Int+maxMnemonicWords = 1023++-- | The most bytes of a share value that fit a mnemonic: the 1016 words+-- left by the metadata hold 10160 bits.+maxShareValueBytes :: Int+maxShareValueBytes = ((maxMnemonicWords - metadataWords) * 10) `div` 8++{- The field GF(256) -}++-- | The powers of the generator x + 1, the byte 3, in the field of the AES+-- cipher: bytes as polynomials modulo x^8 + x^4 + x^3 + x + 1. The table+-- holds 3^i for i from 0 to 254.+expTable :: UArray Int Word8+expTable = listArray (0, 254) (map fromIntegral (take 255 (iterate timesGenerator (1 :: Int))))+ where+ -- Multiply by x + 1, then reduce by the polynomial, 0x11B+ timesGenerator p =+ let q = (p `shiftL` 1) `xor` p+ in if testBit q 8 then q `xor` 0x11B else q++-- | The logarithm of each non-zero byte to the base 3: the inverse of+-- 'expTable'. The entry of zero, which has no logarithm, is unused.+logTable :: UArray Word8 Int+logTable = listArray (0, 255) [M.findWithDefault 0 b logs | b <- [0 .. 255]]+ where+ logs = M.fromList [(expTable ! i, i) | i <- [0 .. 254]]++-- | The product of two bytes in GF(256).+gfMul :: Word8 -> Word8 -> Word8+gfMul a b+ | a == 0 || b == 0 = 0+ | otherwise = expTable ! ((logTable ! a + logTable ! b) `mod` 255)++-- | The inverse of a byte in GF(256); zero has none.+gfInv :: Word8 -> Maybe Word8+gfInv 0 = Nothing+gfInv a = Just (expTable ! ((255 - logTable ! a) `mod` 255))++-- | The quotient of two bytes in GF(256); nothing when dividing by zero.+gfDiv :: Word8 -> Word8 -> Maybe Word8+gfDiv a b = gfMul a <$> gfInv b++{- Shamir's secret sharing -}++-- | The value at the index x of the polynomials through the points given,+-- one polynomial per byte of the values, by Lagrange interpolation. The+-- points must have distinct indices and values of one length.+interpolate :: Word8 -> [(Word8, ByteString)] -> Either String ByteString+interpolate x points = do+ when (null points) $ Left "There are no shares to interpolate"+ when (length (nub indices) /= length indices) $ Left "The shares do not have distinct indices"+ when (length (nub (map (B.length . snd) points)) /= 1) $ Left "The shares do not have the same length"+ weights <- mapM basis indices+ -- Each byte of the result is the sum over the points of the point's byte+ -- times its basis polynomial at x; the sum of the field is xor+ return $ B.pack [foldl' xor 0 (zipWith gfMul weights column) | column <- transpose (map (B.unpack . snd) points)]+ where+ indices = map fst points+ -- The Lagrange basis polynomial of the point at xi, evaluated at x: the+ -- product over the other points of (x - xj) / (xi - xj), where the+ -- difference of the field is xor. The indices are distinct, so no+ -- division is by zero.+ basis xi = foldM (factor xi) 1 [xj | xj <- indices, xj /= xi]+ factor xi acc xj =+ maybe (Left "The shares do not have distinct indices") (Right . gfMul acc) $+ gfDiv (x `xor` xj) (xi `xor` xj)++-- | The random values a split draws: the T - 2 random shares, each as long+-- as the secret, and the random part of the digest share, four bytes+-- shorter. A split with a threshold of 1 draws none.+data SplitRandom = SplitRandom+ { randomShares :: ![ByteString],+ digestRandom :: !ByteString+ }+ deriving (Eq)++-- | The random shares are shares of the secret, and the random part is+-- most of the digest share, so neither is shown.+instance Show SplitRandom where+ showsPrec d _ =+ showParen (d > 10) $+ showString "SplitRandom {randomShares = <redacted>, digestRandom = <redacted>}"++-- | The sizes of the random values a split of a secret of the given length+-- draws at the given threshold: how many random shares, their length, and+-- the length of the random part of the digest share.+splitRandomSizes :: Int -> Int -> (Int, Int, Int)+splitRandomSizes threshold secretLength+ | threshold <= 1 = (0, secretLength, 0)+ | otherwise = (threshold - 2, secretLength, secretLength - digestBytes)++-- | The first four bytes of HMAC-SHA256 keyed with the random part of the+-- digest share, of the shared secret.+digestOf :: ByteString -> ByteString -> ByteString+digestOf randomPart secret = B.take digestBytes (hmacSha256 randomPart secret)++hmacSha256 :: ByteString -> ByteString -> ByteString+hmacSha256 key msg = BA.convert (hmac key msg :: HMAC SHA256)++-- | SplitSecret of the specification: the shares of a secret at the given+-- threshold and count, with their indices from 0, computed from the random+-- values given. With a threshold of 1 every share is the secret itself.+-- Otherwise the random shares take the first indices, the digest share+-- sits at 254 and the secret at 255, and the remaining shares are the+-- polynomials through those points.+splitSecret :: Int -> Int -> ByteString -> SplitRandom -> Either String [(Word8, ByteString)]+splitSecret threshold count secret random = do+ when (threshold < 1) $ Left "The threshold must be at least 1"+ when (threshold > count) $ Left "The threshold is greater than the number of shares"+ when (count > maxShares) $ Left ("The number of shares is greater than " <> show maxShares)+ checkSecretLength "secret" secret+ let (randomCount, _, randomPartLength) = splitRandomSizes threshold (B.length secret)+ unless (length random.randomShares == randomCount) $+ Left ("A split with a threshold of " <> show threshold <> " takes " <> show randomCount <> " random shares")+ unless (all ((== B.length secret) . B.length) random.randomShares) $+ Left "A random share must be as long as the secret"+ unless (B.length random.digestRandom == randomPartLength) $+ Left ("The random part of the digest share must have " <> show randomPartLength <> " bytes")+ if threshold == 1+ then return [(i, secret) | i <- indicesUpTo count]+ else do+ let digestShare = digestOf random.digestRandom secret <> random.digestRandom+ randomPoints = zip (indicesUpTo randomCount) random.randomShares+ basePoints = randomPoints ++ [(digestIndex, digestShare), (secretIndex, secret)]+ computed <- forM (drop randomCount (indicesUpTo count)) $ \i -> (,) i <$> interpolate i basePoints+ return (randomPoints ++ computed)+ where+ indicesUpTo n = take n [0 ..]++-- | RecoverSecret of the specification: the secret the shares were split+-- from at the given threshold. With a threshold of 1 it is the first share.+-- Otherwise the secret and the digest share are interpolated at their+-- indices, and the digest must match the secret.+recoverSecret :: Int -> [(Word8, ByteString)] -> Either String ByteString+recoverSecret threshold shares =+ case shares of+ [] -> Left "There are no shares to recover the secret from"+ (_, first) : _+ | threshold == 1 -> Right first+ | otherwise -> do+ secret <- interpolate secretIndex shares+ digestShare <- interpolate digestIndex shares+ let (digest, randomPart) = B.splitAt digestBytes digestShare+ -- compared in constant time, since the digest is of the secret+ unless (BA.constEq digest (digestOf randomPart secret)) $ Left "The digest of the secret does not match"+ return secret++{- The checksum -}++-- | The polymod of the RS1024 code over the values given, each a word of+-- the mnemonic as its 10-bit index, after the characters of the+-- customization string.+rs1024Polymod :: [Int] -> Int+rs1024Polymod = foldl' step 1+ where+ step chk v =+ let top = chk `shiftR` 20+ shifted = ((chk .&. 0xfffff) `shiftL` 10) `xor` v+ in foldl' (\c (i, g) -> if testBit top i then c `xor` g else c) shifted (zip [0 ..] generator)+ generator =+ [ 0xe0e040,+ 0x1c1c080,+ 0x3838100,+ 0x7070200,+ 0xe0e0009,+ 0x1c0c2412,+ 0x38086c24,+ 0x3090fc48,+ 0x21b1f890,+ 0x3f3f120+ ]++-- | The customization string of the checksum, fed to the polymod before+-- the data: "shamir", or "shamir_extendable" when the extendable flag is+-- set.+customizationString :: Bool -> [Int]+customizationString ext = map ord (if ext then "shamir_extendable" else "shamir")++-- | The three checksum words of the data words given, under the+-- customization string.+rs1024Checksum :: [Int] -> [Int] -> [Int]+rs1024Checksum cs values = [(polymod `shiftR` (10 * (2 - i))) .&. 1023 | i <- [0 .. 2]]+ where+ polymod = rs1024Polymod (cs ++ values ++ [0, 0, 0]) `xor` 1++-- | Whether the words given, the data words and their checksum, check out+-- under the customization string.+rs1024Verify :: [Int] -> [Int] -> Bool+rs1024Verify cs values = rs1024Polymod (cs ++ values) == 1++{- Share mnemonics -}++-- | The mnemonic of a share: its header, its value padded to whole words,+-- and the checksum, as words of the list. A share whose fields are out of+-- range is refused, and so is a value of more than 1270 bytes, whose+-- mnemonic would be longer than the 1023 words the checksum covers.+encodeShare :: Share -> Either String Text+encodeShare share = do+ checkShare share+ when (B.length share.value > maxShareValueBytes) $+ Left ("The share value must have at most " <> show maxShareValueBytes <> " bytes, not " <> show (B.length share.value))+ let dataWords = headerOf share ++ valueWords share.value+ checksum = rs1024Checksum (customizationString share.extendable) dataWords+ indicesToMnemonic (dataWords ++ checksum)++-- | The share a mnemonic carries. The words are looked up in lower case.+-- The mnemonic must have at least 20 and at most 1023 words, a length that+-- leaves at most 8 bits of padding, a valid checksum, zero padding bits,+-- and a group threshold no greater than its group count.+--+-- The words are counted before anything else is done with them, so the+-- work on a mnemonic from an untrusted source is linear in its length.+decodeShare :: Text -> Either String Share+decodeShare mnemonic = do+ let mnemonicWords = T.words mnemonic+ count = length mnemonicWords+ valueWordCount = count - metadataWords+ when (count > maxMnemonicWords) $+ Left ("The mnemonic has " <> show count <> " words, but a share has at most " <> show maxMnemonicWords)+ indices <- wordsToIndices mnemonicWords+ -- The padded value is a whole number of 10-bit words; its length modulo+ -- 16 is the padding, since the value has a multiple of 16 bits+ let padding = (10 * valueWordCount) `mod` 16+ when (count < minMnemonicWords) $+ Left ("The mnemonic has " <> show count <> " words, but a share has at least " <> show minMnemonicWords)+ when (padding > 8) $+ Left ("The mnemonic has " <> show count <> " words, which is not a valid share length")+ let (header, rest) = splitAt headerWords indices+ (valueIndices, checksum) = splitAt valueWordCount rest+ headerInt = indicesToInteger header+ ext = testBit headerInt 24+ unless (rs1024Verify (customizationString ext) (header ++ valueIndices ++ checksum)) $+ Left "The checksum of the mnemonic is invalid"+ let share =+ Share+ { identifier = field 25 32767,+ extendable = ext,+ iterationExponent = field 20 15,+ groupIndex = field 16 15,+ groupThreshold = field 12 15 + 1,+ groupCount = field 8 15 + 1,+ memberIndex = field 4 15,+ memberThreshold = field 0 15 + 1,+ value = B.empty+ }+ field offset mask = fromIntegral ((headerInt `shiftR` offset) .&. mask)+ when (share.groupThreshold > share.groupCount) $+ Left "The group threshold is greater than the group count"+ let valueBytes = (10 * valueWordCount - padding) `div` 8+ valueInt = indicesToInteger valueIndices+ when (valueInt >= 2 ^ (8 * valueBytes)) $+ Left "The padding bits of the share value are not zero"+ return share {value = integerToBytes valueBytes valueInt}++-- | The header of a share as four words: the identifier, the flag, the+-- exponent, the group index, the group threshold less one, the group count+-- less one, the member index and the member threshold less one, 40 bits.+headerOf :: Share -> [Int]+headerOf share = integerToIndices headerWords headerInt+ where+ headerInt =+ (toInteger share.identifier `shiftL` 25)+ .|. (if share.extendable then 1 `shiftL` 24 else 0)+ .|. (toInteger share.iterationExponent `shiftL` 20)+ .|. (toInteger share.groupIndex `shiftL` 16)+ .|. (toInteger (share.groupThreshold - 1) `shiftL` 12)+ .|. (toInteger (share.groupCount - 1) `shiftL` 8)+ .|. (toInteger share.memberIndex `shiftL` 4)+ .|. toInteger (share.memberThreshold - 1)++-- | A share value as words: its bits, left-padded with zeros to a multiple+-- of ten.+valueWords :: ByteString -> [Int]+valueWords bytes = integerToIndices wordCount (bytesToInteger bytes)+ where+ wordCount = (8 * B.length bytes + 9) `div` 10++-- | Every field of a share checked against its range, so that an encoded+-- share is a valid mnemonic and a combined set is one the format allows.+checkShare :: Share -> Either String ()+checkShare share = do+ checkRange "identifier" 0 32767 share.identifier+ checkRange "iteration exponent" 0 15 share.iterationExponent+ checkRange "group index" 0 (maxShares - 1) share.groupIndex+ checkRange "group threshold" 1 maxShares share.groupThreshold+ checkRange "group count" 1 maxShares share.groupCount+ when (share.groupThreshold > share.groupCount) $+ Left "The group threshold is greater than the group count"+ checkRange "member index" 0 (maxShares - 1) share.memberIndex+ checkRange "member threshold" 1 maxShares share.memberThreshold+ checkSecretLength "share value" share.value++-- | A count with its noun: "1 share", "2 shares".+counted :: Int -> String -> String+counted n noun = show n <> " " <> noun <> (if n == 1 then "" else "s")++checkRange :: String -> Int -> Int -> Int -> Either String ()+checkRange what lo hi v =+ unless (v >= lo && v <= hi) $+ Left ("The " <> what <> " must be between " <> show lo <> " and " <> show hi <> ", not " <> show v)++-- | A secret, or a share of one, has at least 128 bits and a multiple of+-- 16 bits.+checkSecretLength :: String -> ByteString -> Either String ()+checkSecretLength what bytes = do+ when (B.length bytes < minSecretBytes) $+ Left ("The " <> what <> " must have at least " <> show minSecretBytes <> " bytes, not " <> show (B.length bytes))+ when (odd (B.length bytes)) $+ Left ("The " <> what <> " must have an even number of bytes, not " <> show (B.length bytes))++{- The passphrase -}++-- | The master secret encrypted with the passphrase under the identifier,+-- the extendable flag and the iteration exponent given, by the four-round+-- Feistel network of the specification. The passphrase must be printable+-- ASCII, code points 32 to 126; use the empty passphrase for none.+encryptMasterSecret :: Int -> Bool -> Int -> Text -> ByteString -> Either String EncryptedMasterSecret+encryptMasterSecret identifier ext iterExponent passphrase secret = do+ checkRange "identifier" 0 32767 identifier+ checkRange "iteration exponent" 0 15 iterExponent+ checkSecretLength "master secret" secret+ passphraseBytes <- checkPassphrase passphrase+ return+ EncryptedMasterSecret+ { identifier = identifier,+ extendable = ext,+ iterationExponent = iterExponent,+ ciphertext = feistel [0, 1, 2, 3] identifier ext iterExponent passphraseBytes secret+ }++-- | The bytes of a passphrase, which must be printable ASCII: "the+-- passphrase MUST be a string containing only printable ASCII characters+-- (code points 32-126)", says the specification.+checkPassphrase :: Text -> Either String ByteString+checkPassphrase passphrase+ | T.all printableAscii passphrase = Right (E.encodeUtf8 passphrase)+ | otherwise = Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ where+ printableAscii c = ord c >= 32 && ord c <= 126++-- | The master secret of an encrypted one under a passphrase: the Feistel+-- rounds in reverse. Any passphrase gives a master secret, so a wrong one+-- is not detected, as the specification intends.+--+-- The passphrase must be printable ASCII, as the specification requires+-- and as on encryption. A passphrase with other characters has several+-- encodings that look alike on screen, such as "é" as one code point or as+-- "e" with a combining accent, and each one would open a different wallet.+-- To recover shares made by software that allowed such a passphrase, use+-- 'decryptMasterSecretBytes' with the exact bytes that software used.+decryptMasterSecret :: Text -> EncryptedMasterSecret -> Either String ByteString+decryptMasterSecret passphrase ems = do+ passphraseBytes <- checkPassphrase passphrase+ decryptMasterSecretBytes passphraseBytes ems++-- | 'decryptMasterSecret' with the passphrase as bytes, which are not+-- checked, as the @decrypt@ function of the reference implementation takes+-- it. This is for shares made by software that allowed a passphrase+-- outside printable ASCII: the caller has to know which bytes that+-- software fed to the cipher, for instance the UTF-8 encoding of the NFKD+-- form of the passphrase. Get the encrypted master secret from+-- 'combineShares'.+decryptMasterSecretBytes :: ByteString -> EncryptedMasterSecret -> Either String ByteString+decryptMasterSecretBytes passphraseBytes ems = do+ checkRange "identifier" 0 32767 ems.identifier+ checkRange "iteration exponent" 0 15 ems.iterationExponent+ checkSecretLength "encrypted master secret" ems.ciphertext+ return $ feistel [3, 2, 1, 0] ems.identifier ems.extendable ems.iterationExponent passphraseBytes ems.ciphertext++-- | The Feistel network of the specification over the rounds given, in+-- that order: the halves L and R become (R, L xor F(i, R)) each round, and+-- the result is R || L. The round function is PBKDF2-HMAC-SHA256 of the+-- round number followed by the passphrase, salted with the salt prefix+-- and R, with 2500 times 2 to the exponent iterations. The passphrase is+-- given as its bytes.+feistel :: [Word8] -> Int -> Bool -> Int -> ByteString -> ByteString -> ByteString+feistel rounds identifier ext iterExponent passphrase secret = right <> left+ where+ half = B.length secret `div` 2+ (left, right) = foldl' step (B.take half secret, B.drop half secret) rounds+ step (l, r) i = (r, B.pack (B.zipWith xor l (roundFunction i r)))+ roundFunction i r =+ fastPBKDF2_SHA256+ Parameters {iterCounts = 2500 `shiftL` iterExponent, outputLength = half}+ (B.cons i passphrase)+ (saltPrefix <> r)+ -- Without the extendable flag the identifier salts the passphrase, so+ -- another split of the same secret gives another master secret+ saltPrefix+ | ext = B.empty+ | otherwise = "shamir" <> integerToBytes 2 (toInteger identifier)++{- Generating shares -}++-- | The shares of a master secret under a scheme, with the random values+-- given: the identifier, then one 'SplitRandom' for the split among the+-- groups and one for each group's split among its members, in that order.+-- GenerateShares of the specification, with the identifier and the+-- extendable flag as arguments. The shares come as one list per group.+generateSharesWith :: Scheme -> Int -> Bool -> Int -> Text -> ByteString -> [SplitRandom] -> Either String [[Share]]+generateSharesWith scheme identifier ext iterExponent passphrase secret randoms = do+ checkScheme scheme+ ems <- encryptMasterSecret identifier ext iterExponent passphrase secret+ (groupRandom, memberRandoms) <- case randoms of+ (g : ms) | length ms == groupCount -> Right (g, ms)+ _ ->+ Left $+ "A scheme of " <> show groupCount <> " groups takes " <> show (groupCount + 1)+ <> " sets of random values: one for the split among the groups, then one per group"+ groupShares <- splitSecret scheme.groupThreshold groupCount ems.ciphertext groupRandom+ forM (zip3 scheme.groups groupShares memberRandoms) $ \((threshold, members), (gi, groupSecret), random) -> do+ memberShares <- splitSecret threshold members groupSecret random+ return+ [ Share+ { identifier = identifier,+ extendable = ext,+ iterationExponent = iterExponent,+ groupIndex = fromIntegral gi,+ groupThreshold = scheme.groupThreshold,+ groupCount = groupCount,+ memberIndex = fromIntegral mi,+ memberThreshold = threshold,+ value = v+ }+ | (mi, v) <- memberShares+ ]+ where+ groupCount = length scheme.groups++-- | A scheme has 1 to 16 groups, a group threshold within them, and in each+-- group a member threshold within 1 to 16 members. A group whose member+-- threshold is 1 has one member: more would differ only in their index and+-- give no security, so the specification refuses them.+checkScheme :: Scheme -> Either String ()+checkScheme scheme = do+ when (null scheme.groups) $ Left "A scheme has at least one group"+ when (length scheme.groups > maxShares) $ Left ("A scheme has at most " <> show maxShares <> " groups")+ checkRange "group threshold" 1 (length scheme.groups) scheme.groupThreshold+ forM_ (zip [1 :: Int ..] scheme.groups) $ \(i, (threshold, members)) -> do+ let group = "group " <> show i+ checkRange ("member count of " <> group) 1 maxShares members+ checkRange ("member threshold of " <> group) 1 members threshold+ when (threshold == 1 && members > 1) $+ Left ("A group with a member threshold of 1 has one member, but " <> group <> " has " <> show members)++-- | The thresholds of the splits of a scheme, in the order the random+-- values are drawn: the groups first, then the members of each group.+schemeThresholds :: Scheme -> [Int]+schemeThresholds scheme = scheme.groupThreshold : map fst scheme.groups++-- | The shares of a master secret under a scheme, with a random identifier+-- and random values from the operating system: 'getEntropy' of the entropy+-- package, which reads the @getrandom@ system call or @\/dev\/urandom@ on+-- Unix and the system's generator on Windows. The specification asks for a+-- source "suitable for generating cryptographic keys".+--+-- Everything is checked before anything is drawn. A secret of more than+-- 1270 bytes can be split, but its shares are too long for 'encodeShare'.+generateShares :: (MonadIO m) => Scheme -> Bool -> Int -> Text -> ByteString -> m (Either String [[Share]])+generateShares = generateSharesFrom (liftIO . getEntropy)++-- | 'generateShares' with the source of random bytes as its first+-- argument: a function from a number of bytes to that many random bytes.+-- Use it to draw from a generator of your own, which must be fit for keys.+-- With @getRandomBytes@ of crypton it runs in any @MonadRandom@. Mind that+-- in 'IO' that function takes its bytes from the RDRAND instruction of the+-- processor alone when the processor has one, and does not mix in the+-- operating system's entropy.+--+-- The source is only asked for whole values, never for zero bytes, and+-- only after the scheme, the exponent, the passphrase and the secret have+-- passed their checks.+generateSharesFrom ::+ (Monad m) =>+ -- | the source of random bytes+ (Int -> m ByteString) ->+ Scheme ->+ Bool ->+ Int ->+ Text ->+ ByteString ->+ m (Either String [[Share]])+generateSharesFrom randomBytes scheme ext iterExponent passphrase secret = runExceptT $ do+ ExceptT . return $ do+ checkScheme scheme+ checkRange "iteration exponent" 0 15 iterExponent+ checkSecretLength "master secret" secret+ _ <- checkPassphrase passphrase+ return ()+ idBytes <- draw 2+ let identifier = fromIntegral (bytesToInteger idBytes .&. 0x7fff)+ randoms <- mapM drawSplitRandom (schemeThresholds scheme)+ ExceptT . return $ generateSharesWith scheme identifier ext iterExponent passphrase secret randoms+ where+ -- The bytes asked of the source, which must give as many as asked+ draw 0 = return B.empty+ draw n = do+ bytes <- lift (randomBytes n)+ unless (B.length bytes == n) $+ throwE ("The source of random bytes gave " <> show (B.length bytes) <> " bytes where " <> show n <> " were asked for")+ return bytes+ -- The random values of one split: the random shares, then the random+ -- part of the digest share+ drawSplitRandom threshold = do+ let (count, shareLength, randomPartLength) = splitRandomSizes threshold (B.length secret)+ shares <- replicateM count (draw shareLength)+ randomPart <- draw randomPartLength+ return SplitRandom {randomShares = shares, digestRandom = randomPart}++-- | The share mnemonics of a master secret under a scheme, one list per+-- group: 'generateShares' encoded.+generateMnemonics :: (MonadIO m) => Scheme -> Bool -> Int -> Text -> ByteString -> m (Either String [[Text]])+generateMnemonics scheme ext iterExponent passphrase secret =+ (>>= traverse (traverse encodeShare)) <$> generateShares scheme ext iterExponent passphrase secret++{- Combining shares -}++-- | The encrypted master secret the shares rebuild. The shares must agree+-- on the identifier, the flag, the exponent, the group threshold, the group+-- count and the length of their values; they must come from exactly as+-- many groups as the group threshold; and in each group they must agree on+-- the member threshold, have distinct member indices and be exactly as many+-- as that threshold. Each group's secret is recovered from its members,+-- then the master secret from the groups.+combineShares :: [Share] -> Either String EncryptedMasterSecret+combineShares [] = Left "There are no shares to combine"+combineShares shares@(first : _) = do+ mapM_ checkShare shares+ agree "identifier" (.identifier)+ agree "extendable flag" (.extendable)+ agree "iteration exponent" (.iterationExponent)+ agree "group threshold" (.groupThreshold)+ agree "group count" (.groupCount)+ agree "length of their values" (B.length . (.value))+ let groups = M.toList (M.fromListWith (flip (++)) [(s.groupIndex, [s]) | s <- shares])+ unless (length groups == first.groupThreshold) $+ Left ("The shares come from " <> counted (length groups) "group" <> ", but the group threshold is " <> show first.groupThreshold)+ groupSecrets <- forM groups $ \(gi, members) -> do+ threshold <- case members of+ m : _ -> Right m.memberThreshold+ [] -> Left ("Group " <> show gi <> " has no shares") -- cannot happen: a group holds the share it was made for+ unless (all ((== threshold) . (.memberThreshold)) members) $+ Left ("The shares of group " <> show gi <> " do not have the same member threshold")+ forM_ (duplicates (map (.memberIndex) members)) $ \mi ->+ Left ("Group " <> show gi <> " has two shares with the member index " <> show mi)+ unless (length members == threshold) $+ Left ("Group " <> show gi <> " has " <> counted (length members) "share" <> ", but its member threshold is " <> show threshold)+ secret <- recoverSecret threshold [(fromIntegral m.memberIndex, m.value) | m <- members]+ return (fromIntegral gi, secret)+ ciphertext <- recoverSecret first.groupThreshold groupSecrets+ return+ EncryptedMasterSecret+ { identifier = first.identifier,+ extendable = first.extendable,+ iterationExponent = first.iterationExponent,+ ciphertext = ciphertext+ }+ where+ agree :: (Eq a) => String -> (Share -> a) -> Either String ()+ agree what field =+ unless (all ((== field first) . field) shares) $+ Left ("The shares do not have the same " <> what)+ duplicates xs = nub [x | (i, x) <- zip [0 :: Int ..] xs, x `elem` take i xs]++-- | The master secret the mnemonics rebuild, decrypted with the passphrase,+-- which must be printable ASCII (see 'decryptMasterSecret').+--+-- The cost of the decryption is set by the iteration exponent the shares+-- carry: 10000 times 2 to its power rounds of PBKDF2-HMAC-SHA256. The+-- exponent can be as high as 15, which is 327,680,000 rounds and about two+-- minutes of one processor core, where an exponent of 0 takes a few+-- milliseconds. The specification allows it. A program that reads shares+-- from an untrusted source should look at the @iterationExponent@ that+-- 'decodeShare' gives before it combines them.+combineMnemonics :: Text -> [Text] -> Either String ByteString+combineMnemonics passphrase mnemonics = do+ shares <- mapM decodeShare mnemonics+ ems <- combineShares shares+ decryptMasterSecret passphrase ems++{- Words and bits -}++-- | The indices of the words of a mnemonic in the list. The words are+-- separated by white space and matched without regard to the case of+-- their ASCII letters. An error names the position of the first word that+-- is not in the list, counted from 1, and not the word, since a share is a+-- secret.+mnemonicToIndices :: Text -> Either String [Int]+mnemonicToIndices = wordsToIndices . T.words++-- | The indices of words in the list, see 'mnemonicToIndices'.+wordsToIndices :: [Text] -> Either String [Int]+wordsToIndices ws = forM (zip [1 :: Int ..] ws) $ \(position, w) ->+ maybe (Left ("Word " <> show position <> " is not in the SLIP-39 word list")) Right (wordIndex w)++-- | The index of a word in the list. The words of the list are ASCII, so+-- a word with any other character is not one of them: lower-casing by the+-- Unicode rules would turn a look-alike such as U+212A KELVIN SIGN into a+-- letter.+wordIndex :: Text -> Maybe Int+wordIndex w+ | T.all isAscii w = M.lookup (T.toLower w) slip39WordListMap+ | otherwise = Nothing++-- | The mnemonic of word indices, the words separated by single spaces.+-- An index outside the list, 0 to 1023, is refused.+indicesToMnemonic :: [Int] -> Either String Text+indicesToMnemonic indices = T.unwords <$> mapM wordAt indices+ where+ wordAt i = maybe (Left ("The word index must be between 0 and 1023, not " <> show i)) Right (slip39WordList V.!? i)++-- | The big-endian number of a list of 10-bit indices.+indicesToInteger :: [Int] -> Integer+indicesToInteger = foldl' (\acc i -> (acc `shiftL` 10) .|. toInteger i) 0++-- | A number as the given count of 10-bit indices, big-endian.+integerToIndices :: Int -> Integer -> [Int]+integerToIndices count n = [fromIntegral ((n `shiftR` (10 * i)) .&. 1023) | i <- reverse [0 .. count - 1]]++-- | The big-endian number of a byte string.+bytesToInteger :: ByteString -> Integer+bytesToInteger = B.foldl' (\acc b -> (acc `shiftL` 8) .|. toInteger b) 0++-- | A number as the given count of bytes, big-endian.+integerToBytes :: Int -> Integer -> ByteString+integerToBytes count n = B.pack [fromIntegral ((n `shiftR` (8 * i)) .&. 255) | i <- reverse [0 .. count - 1]]++-- | The index of each word of the list.+slip39WordListMap :: Map Text Int+slip39WordListMap = M.fromList (zip (V.toList slip39WordList) [0 ..])++-- | The 1024 words of SLIP-0039, in order. The list is alphabetical, its+-- words have 4 to 8 letters and distinct first four letters.+slip39WordList :: Vector Text+slip39WordList =+ V.fromListN+ 1024+ [ "academic",+ "acid",+ "acne",+ "acquire",+ "acrobat",+ "activity",+ "actress",+ "adapt",+ "adequate",+ "adjust",+ "admit",+ "adorn",+ "adult",+ "advance",+ "advocate",+ "afraid",+ "again",+ "agency",+ "agree",+ "aide",+ "aircraft",+ "airline",+ "airport",+ "ajar",+ "alarm",+ "album",+ "alcohol",+ "alien",+ "alive",+ "alpha",+ "already",+ "alto",+ "aluminum",+ "always",+ "amazing",+ "ambition",+ "amount",+ "amuse",+ "analysis",+ "anatomy",+ "ancestor",+ "ancient",+ "angel",+ "angry",+ "animal",+ "answer",+ "antenna",+ "anxiety",+ "apart",+ "aquatic",+ "arcade",+ "arena",+ "argue",+ "armed",+ "artist",+ "artwork",+ "aspect",+ "auction",+ "august",+ "aunt",+ "average",+ "aviation",+ "avoid",+ "award",+ "away",+ "axis",+ "axle",+ "beam",+ "beard",+ "beaver",+ "become",+ "bedroom",+ "behavior",+ "being",+ "believe",+ "belong",+ "benefit",+ "best",+ "beyond",+ "bike",+ "biology",+ "birthday",+ "bishop",+ "black",+ "blanket",+ "blessing",+ "blimp",+ "blind",+ "blue",+ "body",+ "bolt",+ "boring",+ "born",+ "both",+ "boundary",+ "bracelet",+ "branch",+ "brave",+ "breathe",+ "briefing",+ "broken",+ "brother",+ "browser",+ "bucket",+ "budget",+ "building",+ "bulb",+ "bulge",+ "bumpy",+ "bundle",+ "burden",+ "burning",+ "busy",+ "buyer",+ "cage",+ "calcium",+ "camera",+ "campus",+ "canyon",+ "capacity",+ "capital",+ "capture",+ "carbon",+ "cards",+ "careful",+ "cargo",+ "carpet",+ "carve",+ "category",+ "cause",+ "ceiling",+ "center",+ "ceramic",+ "champion",+ "change",+ "charity",+ "check",+ "chemical",+ "chest",+ "chew",+ "chubby",+ "cinema",+ "civil",+ "class",+ "clay",+ "cleanup",+ "client",+ "climate",+ "clinic",+ "clock",+ "clogs",+ "closet",+ "clothes",+ "club",+ "cluster",+ "coal",+ "coastal",+ "coding",+ "column",+ "company",+ "corner",+ "costume",+ "counter",+ "course",+ "cover",+ "cowboy",+ "cradle",+ "craft",+ "crazy",+ "credit",+ "cricket",+ "criminal",+ "crisis",+ "critical",+ "crowd",+ "crucial",+ "crunch",+ "crush",+ "crystal",+ "cubic",+ "cultural",+ "curious",+ "curly",+ "custody",+ "cylinder",+ "daisy",+ "damage",+ "dance",+ "darkness",+ "database",+ "daughter",+ "deadline",+ "deal",+ "debris",+ "debut",+ "decent",+ "decision",+ "declare",+ "decorate",+ "decrease",+ "deliver",+ "demand",+ "density",+ "deny",+ "depart",+ "depend",+ "depict",+ "deploy",+ "describe",+ "desert",+ "desire",+ "desktop",+ "destroy",+ "detailed",+ "detect",+ "device",+ "devote",+ "diagnose",+ "dictate",+ "diet",+ "dilemma",+ "diminish",+ "dining",+ "diploma",+ "disaster",+ "discuss",+ "disease",+ "dish",+ "dismiss",+ "display",+ "distance",+ "dive",+ "divorce",+ "document",+ "domain",+ "domestic",+ "dominant",+ "dough",+ "downtown",+ "dragon",+ "dramatic",+ "dream",+ "dress",+ "drift",+ "drink",+ "drove",+ "drug",+ "dryer",+ "duckling",+ "duke",+ "duration",+ "dwarf",+ "dynamic",+ "early",+ "earth",+ "easel",+ "easy",+ "echo",+ "eclipse",+ "ecology",+ "edge",+ "editor",+ "educate",+ "either",+ "elbow",+ "elder",+ "election",+ "elegant",+ "element",+ "elephant",+ "elevator",+ "elite",+ "else",+ "email",+ "emerald",+ "emission",+ "emperor",+ "emphasis",+ "employer",+ "empty",+ "ending",+ "endless",+ "endorse",+ "enemy",+ "energy",+ "enforce",+ "engage",+ "enjoy",+ "enlarge",+ "entrance",+ "envelope",+ "envy",+ "epidemic",+ "episode",+ "equation",+ "equip",+ "eraser",+ "erode",+ "escape",+ "estate",+ "estimate",+ "evaluate",+ "evening",+ "evidence",+ "evil",+ "evoke",+ "exact",+ "example",+ "exceed",+ "exchange",+ "exclude",+ "excuse",+ "execute",+ "exercise",+ "exhaust",+ "exotic",+ "expand",+ "expect",+ "explain",+ "express",+ "extend",+ "extra",+ "eyebrow",+ "facility",+ "fact",+ "failure",+ "faint",+ "fake",+ "false",+ "family",+ "famous",+ "fancy",+ "fangs",+ "fantasy",+ "fatal",+ "fatigue",+ "favorite",+ "fawn",+ "fiber",+ "fiction",+ "filter",+ "finance",+ "findings",+ "finger",+ "firefly",+ "firm",+ "fiscal",+ "fishing",+ "fitness",+ "flame",+ "flash",+ "flavor",+ "flea",+ "flexible",+ "flip",+ "float",+ "floral",+ "fluff",+ "focus",+ "forbid",+ "force",+ "forecast",+ "forget",+ "formal",+ "fortune",+ "forward",+ "founder",+ "fraction",+ "fragment",+ "frequent",+ "freshman",+ "friar",+ "fridge",+ "friendly",+ "frost",+ "froth",+ "frozen",+ "fumes",+ "funding",+ "furl",+ "fused",+ "galaxy",+ "game",+ "garbage",+ "garden",+ "garlic",+ "gasoline",+ "gather",+ "general",+ "genius",+ "genre",+ "genuine",+ "geology",+ "gesture",+ "glad",+ "glance",+ "glasses",+ "glen",+ "glimpse",+ "goat",+ "golden",+ "graduate",+ "grant",+ "grasp",+ "gravity",+ "gray",+ "greatest",+ "grief",+ "grill",+ "grin",+ "grocery",+ "gross",+ "group",+ "grownup",+ "grumpy",+ "guard",+ "guest",+ "guilt",+ "guitar",+ "gums",+ "hairy",+ "hamster",+ "hand",+ "hanger",+ "harvest",+ "have",+ "havoc",+ "hawk",+ "hazard",+ "headset",+ "health",+ "hearing",+ "heat",+ "helpful",+ "herald",+ "herd",+ "hesitate",+ "hobo",+ "holiday",+ "holy",+ "home",+ "hormone",+ "hospital",+ "hour",+ "huge",+ "human",+ "humidity",+ "hunting",+ "husband",+ "hush",+ "husky",+ "hybrid",+ "idea",+ "identify",+ "idle",+ "image",+ "impact",+ "imply",+ "improve",+ "impulse",+ "include",+ "income",+ "increase",+ "index",+ "indicate",+ "industry",+ "infant",+ "inform",+ "inherit",+ "injury",+ "inmate",+ "insect",+ "inside",+ "install",+ "intend",+ "intimate",+ "invasion",+ "involve",+ "iris",+ "island",+ "isolate",+ "item",+ "ivory",+ "jacket",+ "jerky",+ "jewelry",+ "join",+ "judicial",+ "juice",+ "jump",+ "junction",+ "junior",+ "junk",+ "jury",+ "justice",+ "kernel",+ "keyboard",+ "kidney",+ "kind",+ "kitchen",+ "knife",+ "knit",+ "laden",+ "ladle",+ "ladybug",+ "lair",+ "lamp",+ "language",+ "large",+ "laser",+ "laundry",+ "lawsuit",+ "leader",+ "leaf",+ "learn",+ "leaves",+ "lecture",+ "legal",+ "legend",+ "legs",+ "lend",+ "length",+ "level",+ "liberty",+ "library",+ "license",+ "lift",+ "likely",+ "lilac",+ "lily",+ "lips",+ "liquid",+ "listen",+ "literary",+ "living",+ "lizard",+ "loan",+ "lobe",+ "location",+ "losing",+ "loud",+ "loyalty",+ "luck",+ "lunar",+ "lunch",+ "lungs",+ "luxury",+ "lying",+ "lyrics",+ "machine",+ "magazine",+ "maiden",+ "mailman",+ "main",+ "makeup",+ "making",+ "mama",+ "manager",+ "mandate",+ "mansion",+ "manual",+ "marathon",+ "march",+ "market",+ "marvel",+ "mason",+ "material",+ "math",+ "maximum",+ "mayor",+ "meaning",+ "medal",+ "medical",+ "member",+ "memory",+ "mental",+ "merchant",+ "merit",+ "method",+ "metric",+ "midst",+ "mild",+ "military",+ "mineral",+ "minister",+ "miracle",+ "mixed",+ "mixture",+ "mobile",+ "modern",+ "modify",+ "moisture",+ "moment",+ "morning",+ "mortgage",+ "mother",+ "mountain",+ "mouse",+ "move",+ "much",+ "mule",+ "multiple",+ "muscle",+ "museum",+ "music",+ "mustang",+ "nail",+ "national",+ "necklace",+ "negative",+ "nervous",+ "network",+ "news",+ "nuclear",+ "numb",+ "numerous",+ "nylon",+ "oasis",+ "obesity",+ "object",+ "observe",+ "obtain",+ "ocean",+ "often",+ "olympic",+ "omit",+ "oral",+ "orange",+ "orbit",+ "order",+ "ordinary",+ "organize",+ "ounce",+ "oven",+ "overall",+ "owner",+ "paces",+ "pacific",+ "package",+ "paid",+ "painting",+ "pajamas",+ "pancake",+ "pants",+ "papa",+ "paper",+ "parcel",+ "parking",+ "party",+ "patent",+ "patrol",+ "payment",+ "payroll",+ "peaceful",+ "peanut",+ "peasant",+ "pecan",+ "penalty",+ "pencil",+ "percent",+ "perfect",+ "permit",+ "petition",+ "phantom",+ "pharmacy",+ "photo",+ "phrase",+ "physics",+ "pickup",+ "picture",+ "piece",+ "pile",+ "pink",+ "pipeline",+ "pistol",+ "pitch",+ "plains",+ "plan",+ "plastic",+ "platform",+ "playoff",+ "pleasure",+ "plot",+ "plunge",+ "practice",+ "prayer",+ "preach",+ "predator",+ "pregnant",+ "premium",+ "prepare",+ "presence",+ "prevent",+ "priest",+ "primary",+ "priority",+ "prisoner",+ "privacy",+ "prize",+ "problem",+ "process",+ "profile",+ "program",+ "promise",+ "prospect",+ "provide",+ "prune",+ "public",+ "pulse",+ "pumps",+ "punish",+ "puny",+ "pupal",+ "purchase",+ "purple",+ "python",+ "quantity",+ "quarter",+ "quick",+ "quiet",+ "race",+ "racism",+ "radar",+ "railroad",+ "rainbow",+ "raisin",+ "random",+ "ranked",+ "rapids",+ "raspy",+ "reaction",+ "realize",+ "rebound",+ "rebuild",+ "recall",+ "receiver",+ "recover",+ "regret",+ "regular",+ "reject",+ "relate",+ "remember",+ "remind",+ "remove",+ "render",+ "repair",+ "repeat",+ "replace",+ "require",+ "rescue",+ "research",+ "resident",+ "response",+ "result",+ "retailer",+ "retreat",+ "reunion",+ "revenue",+ "review",+ "reward",+ "rhyme",+ "rhythm",+ "rich",+ "rival",+ "river",+ "robin",+ "rocky",+ "romantic",+ "romp",+ "roster",+ "round",+ "royal",+ "ruin",+ "ruler",+ "rumor",+ "sack",+ "safari",+ "salary",+ "salon",+ "salt",+ "satisfy",+ "satoshi",+ "saver",+ "says",+ "scandal",+ "scared",+ "scatter",+ "scene",+ "scholar",+ "science",+ "scout",+ "scramble",+ "screw",+ "script",+ "scroll",+ "seafood",+ "season",+ "secret",+ "security",+ "segment",+ "senior",+ "shadow",+ "shaft",+ "shame",+ "shaped",+ "sharp",+ "shelter",+ "sheriff",+ "short",+ "should",+ "shrimp",+ "sidewalk",+ "silent",+ "silver",+ "similar",+ "simple",+ "single",+ "sister",+ "skin",+ "skunk",+ "slap",+ "slavery",+ "sled",+ "slice",+ "slim",+ "slow",+ "slush",+ "smart",+ "smear",+ "smell",+ "smirk",+ "smith",+ "smoking",+ "smug",+ "snake",+ "snapshot",+ "sniff",+ "society",+ "software",+ "soldier",+ "solution",+ "soul",+ "source",+ "space",+ "spark",+ "speak",+ "species",+ "spelling",+ "spend",+ "spew",+ "spider",+ "spill",+ "spine",+ "spirit",+ "spit",+ "spray",+ "sprinkle",+ "square",+ "squeeze",+ "stadium",+ "staff",+ "standard",+ "starting",+ "station",+ "stay",+ "steady",+ "step",+ "stick",+ "stilt",+ "story",+ "strategy",+ "strike",+ "style",+ "subject",+ "submit",+ "sugar",+ "suitable",+ "sunlight",+ "superior",+ "surface",+ "surprise",+ "survive",+ "sweater",+ "swimming",+ "swing",+ "switch",+ "symbolic",+ "sympathy",+ "syndrome",+ "system",+ "tackle",+ "tactics",+ "tadpole",+ "talent",+ "task",+ "taste",+ "taught",+ "taxi",+ "teacher",+ "teammate",+ "teaspoon",+ "temple",+ "tenant",+ "tendency",+ "tension",+ "terminal",+ "testify",+ "texture",+ "thank",+ "that",+ "theater",+ "theory",+ "therapy",+ "thorn",+ "threaten",+ "thumb",+ "thunder",+ "ticket",+ "tidy",+ "timber",+ "timely",+ "ting",+ "tofu",+ "together",+ "tolerate",+ "total",+ "toxic",+ "tracks",+ "traffic",+ "training",+ "transfer",+ "trash",+ "traveler",+ "treat",+ "trend",+ "trial",+ "tricycle",+ "trip",+ "triumph",+ "trouble",+ "true",+ "trust",+ "twice",+ "twin",+ "type",+ "typical",+ "ugly",+ "ultimate",+ "umbrella",+ "uncover",+ "undergo",+ "unfair",+ "unfold",+ "unhappy",+ "union",+ "universe",+ "unkind",+ "unknown",+ "unusual",+ "unwrap",+ "upgrade",+ "upstairs",+ "username",+ "usher",+ "usual",+ "valid",+ "valuable",+ "vampire",+ "vanish",+ "various",+ "vegan",+ "velvet",+ "venture",+ "verdict",+ "verify",+ "very",+ "veteran",+ "vexed",+ "victim",+ "video",+ "view",+ "vintage",+ "violence",+ "viral",+ "visitor",+ "visual",+ "vitamins",+ "vocal",+ "voice",+ "volume",+ "voter",+ "voting",+ "walnut",+ "warmth",+ "warn",+ "watch",+ "wavy",+ "wealthy",+ "weapon",+ "webcam",+ "welcome",+ "welfare",+ "western",+ "width",+ "wildlife",+ "window",+ "wine",+ "wireless",+ "wisdom",+ "withdraw",+ "wits",+ "wolf",+ "woman",+ "work",+ "worthy",+ "wrap",+ "wrist",+ "writing",+ "wrote",+ "year",+ "yelp",+ "yield",+ "yoga",+ "zero"+ ]
src/Haskoin/Crypto/Signature.hs view
@@ -25,20 +25,18 @@ ) where -import Control.Monad (guard, unless, when)+import Control.Monad (unless, when) import Crypto.Secp256k1 import Data.Aeson import Data.Aeson.Encoding import Data.Binary (Binary (..)) import Data.ByteString (ByteString)-import Data.ByteString qualified as B import Data.ByteString.Lazy qualified as L import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Maybe (fromMaybe) import Data.Serialize (Serialize (..))-import Data.Text qualified as T import Haskoin.Crypto.Hash import Haskoin.Util.Helpers import Haskoin.Util.Marshal@@ -81,27 +79,33 @@ 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.+ -- Decoding applies the same rules as the binary form, 'decodeStrictSig'.+ -- The error message does not repeat the text. The text comes from outside+ -- and can be of any length. unmarshalValue ctx = withText "Sig" $ \t -> case decodeHex t >>= decodeStrictSig ctx of- Nothing -> fail $ "Could not decode signature: " <> T.unpack t+ Nothing -> fail "Could not decode signature" Just s -> return s --- | Is canonical half order: the s component is not above half the curve--- order, the only form 'verifySig' accepts.+-- | Is the s component at most half the curve order? This is the low-S rule+-- that Bitcoin Core applies to the transactions it relays:+-- @SCRIPT_VERIFY_LOW_S@ is one of its standard flags. It is not a consensus+-- rule, so a block may hold a signature with a high s. The decoders of this+-- module do not apply it. Check it with this function where the relay rules+-- matter. 'signHash' always makes a signature that passes. isCanonicalHalfOrder :: Ctx -> Sig -> Bool isCanonicalHalfOrder = isLowS --- | Decode signature strictly.+-- | Decode a signature from its strict DER encoding, the encoding that BIP 66+-- made a consensus rule (@IsValidSignatureEncoding@ in Bitcoin Core, without+-- the hash type byte).+--+-- A signature whose r or s is zero, or not below the curve order, is refused+-- as well. BIP 66 allows such an encoding, but the signature can never+-- verify. 'importSig' applies both rules.+--+-- A high s is accepted, since BIP 66 allows it and such signatures are in the+-- block chain. Use 'isCanonicalHalfOrder' for the low-S relay rule. decodeStrictSig :: Ctx -> ByteString -> Maybe Sig-decodeStrictSig ctx bs = do- g <- importSig ctx bs- -- <http://www.secg.org/sec1-v2.pdf Section 4.1.4>- -- 4.1.4.1 (r and s can not be zero)- let compact = exportCompactSig ctx g- let zero = B.replicate 32 0- guard $ B.take 32 compact.get /= zero- guard $ (B.take 32 . B.drop 32) compact.get /= zero- guard $ isCanonicalHalfOrder ctx g- return g+decodeStrictSig = importSig
src/Haskoin/Network/BlockFilter.hs view
@@ -143,13 +143,15 @@ } deriving (Eq, Show, Generic, NFData) +-- | A message of more than 'maxFilterHashes' filter hashes fails the parse,+-- as BIP 157 requires. instance Serial CFHeaders where deserialize = CFHeaders <$> getWord8 <*> deserialize <*> deserialize- <*> getVarIntList deserialize+ <*> getVarIntListMax "cfheaders" maxFilterHashes deserialize serialize c = do putWord8 c.filterType serialize c.stopHash
src/Haskoin/Network/Bloom.hs view
@@ -43,7 +43,7 @@ import Control.Applicative ((<|>)) import Control.DeepSeq-import Control.Monad (when)+import Control.Monad (guard, when) import Crypto.Secp256k1 (Ctx) import Data.Binary (Binary (..)) import Data.Bits@@ -55,6 +55,7 @@ import Data.Foldable qualified as F import Data.Hash.Murmur (murmur3) import Data.List (foldl')+import Data.Maybe (fromMaybe) import Data.Sequence qualified as S import Data.Serialize (Serialize (..)) import Data.Serialize qualified as Ser@@ -62,9 +63,7 @@ import GHC.Generics (Generic) import Haskoin.Network.Common import Haskoin.Script.Common-import Haskoin.Script.Standard import Haskoin.Transaction.Common-import Haskoin.Util.Marshal -- | Largest filter a peer may load, in bytes. 20,000 items with fp rate -- < 0.1% or 10,000 items and <0.0001%.@@ -241,8 +240,10 @@ numHashF | numElem == 0 = 0 | otherwise = truncate $ min c (fromIntegral maxHashFuncs)- -- Suggested number of hash functions- c = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2+ -- Suggested number of hash functions. The bits per element are divided+ -- as integers first, as in the constructor of CBloomFilter in Bitcoin+ -- Core, so that both pick the same number.+ c = fromIntegral ((bloomSize * 8) `div` numElem) * ln2 bloomHash :: BloomFilter -> Word32 -> ByteString -> Word32 bloomHash b hashNum bs =@@ -313,30 +314,47 @@ | 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.+-- | Tells whether a transaction is relevant to a filter, and updates the+-- filter, as @CBloomFilter::IsRelevantAndUpdate@ does in Bitcoin Core and as+-- BIP 37 specifies. A transaction is relevant when the filter contains:+--+-- 1. its hash, or+-- 2. a data element pushed by one of its output scripts, or+-- 3. an outpoint that one of its inputs spends, or+-- 4. a data element pushed by one of its input scripts.+--+-- The result is 'Nothing' for a transaction that is not relevant, and+-- otherwise the filter. For each output that matches, its outpoint is+-- inserted according to the flags: always with 'BloomUpdateAll', only for+-- pay-to-pubkey and bare multisig outputs with 'BloomUpdateP2PubKeyOnly', and+-- never with 'BloomUpdateNone'. This lets a later transaction that spends the+-- output match through rule 3. As in Bitcoin Core, the inputs are looked at+-- only when neither the hash nor an output matched.+--+-- Pay-to-pubkey and multisig are told by the shape of the script, as+-- @Solver@ does in Bitcoin Core: a key is any push of 33 bytes that starts+-- with 02 or 03, or of 65 bytes that starts with 04, 06 or 07. It does not+-- have to be a point of the curve. Non-standard scripts are scanned like any+-- other. An empty filter matches every transaction and gets no outpoints.+--+-- The context is not used. It is kept so that callers do not have to change. bloomRelevantUpdate :: Ctx -> -- | Bloom filter BloomFilter ->- -- | Tx that may (or may not) have relevant outputs+ -- | transaction to test Tx ->- -- | Returns an updated bloom filter adding relevant output+ -- | the filter, updated, when the transaction is relevant Maybe BloomFilter-bloomRelevantUpdate ctx b tx+bloomRelevantUpdate _ b tx | S.null b.array = Just b- | found = Just updated+ | hashFound || outputFound = Just updated+ | any inputMatches tx.inputs = Just b | otherwise = Nothing where- (found, updated) = foldl' step (False, b) (zip [0 ..] tx.outputs)+ hash = txHash tx+ hashFound = bloomContains b (runPutS (serialize hash))+ (outputFound, 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) =@@ -349,11 +367,70 @@ 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+ outpoint i = runPutS $ serialize $ OutPoint {hash = hash, index = i}+ inputMatches :: TxIn -> Bool+ inputMatches input =+ bloomContains b (runPutS (serialize input.outpoint))+ || any (bloomContains b) (scriptPushData input.script)++-- | Is the script pay-to-pubkey or bare multisig, as @Solver@ classifies them+-- in Bitcoin Core (@MatchPayToPubkey@ and @MatchMultisig@)? Keys are tested+-- by size and first byte only.+paysToKeys :: ByteString -> Bool+paysToKeys script = payToPubkey || multisig+ where+ opCheckSig = 0xac+ payToPubkey =+ or+ [ BS.length script == size + 2+ && BS.head script == fromIntegral size+ && BS.last script == opCheckSig+ && validKeySize (BS.take size (BS.drop 1 script))+ | size <- [65, 33]+ ]+ multisig =+ case runGetS deserialize script :: Either String Script of+ Right (Script (m : rest)) ->+ case span isKey rest of+ (keys, [n, OP_CHECKMULTISIG]) -> fromMaybe False $ do+ required <- scriptNumber m+ guard (required >= 1 && required <= maxKeys)+ total <- scriptNumber n+ guard (total >= required && total <= maxKeys)+ return (length keys == total)+ _ -> False+ _ -> False+ -- MAX_PUBKEYS_PER_MULTISIG+ maxKeys = 20+ isKey (OP_PUSHDATA bs _) = validKeySize bs+ isKey _ = False++-- | @CPubKey::ValidSize@ of Bitcoin Core: 33 bytes after 02 or 03, 65 bytes+-- after 04, 06 or 07.+validKeySize :: ByteString -> Bool+validKeySize key =+ case BS.uncons key of+ Just (prefix, _)+ | prefix == 2 || prefix == 3 -> BS.length key == 33+ | prefix == 4 || prefix == 6 || prefix == 7 -> BS.length key == 65+ _ -> False++-- | The number that a script operation stands for in a multisig template,+-- as @GetScriptNumber@ reads it in Bitcoin Core: 'OP_1' to 'OP_16', or a+-- minimal push of one byte for 17 and above.+scriptNumber :: ScriptOp -> Maybe Int+scriptNumber op =+ case op of+ OP_PUSHDATA bs OPCODE+ | BS.length bs == 1 && BS.head bs > 16 && BS.head bs < 0x80 ->+ Just (fromIntegral (BS.head bs))+ OP_PUSHDATA {} -> Nothing+ OP_0 -> Nothing+ OP_1NEGATE -> Nothing+ _ -> do+ bs <- pushOpData op+ guard (BS.length bs == 1)+ return (fromIntegral (BS.head bs)) -- | Returns True if the filter is empty (all bytes set to 0x00) isBloomEmpty :: BloomFilter -> Bool
src/Haskoin/Network/Common.hs view
@@ -65,17 +65,24 @@ stringToCommand, putVarInt, getVarIntList,+ getVarIntListMax, putVarIntList, getVarIntBytes, putVarIntBytes, maxListSize,+ maxInvSize,+ maxAddrSize,+ maxHeadersSize,+ maxLocatorSize,+ maxFilterHashes, ) where import Control.DeepSeq-import Control.Monad (forM_, liftM2, replicateM, unless, when)+import Control.Monad (forM_, liftM2, unless, when) import Data.Binary (Binary (..)) import Data.Bits (shiftL, shiftR, (.&.), (.|.))+import Data.Int (Int32, Int64) import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.Bytes.Get@@ -102,8 +109,9 @@ deriving (Eq, Show, Generic) deriving newtype (NFData) +-- | A message with more than 'maxAddrSize' entries fails the parse. instance Serial Addr where- deserialize = Addr <$> getVarIntList action+ deserialize = Addr <$> getVarIntListMax "addr" maxAddrSize action where action = liftM2 (,) getWord32le deserialize @@ -193,7 +201,10 @@ -- | 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.+-- networks are kept with their bytes. So are Tor v2 entries, whatever their+-- length: BIP 155 says that clients "MUST ignore them on receive", and+-- Bitcoin Core skips them without failing the message. A caller must not+-- relay a Tor v2 entry. instance Serial NetworkAddressV2 where deserialize = do time <- getWord32le@@ -203,7 +214,10 @@ when (len > maxAddrV2Size) $ fail ("addrv2 address too long: " ++ show len) address <- getByteString (fromIntegral len)- forM_ (addressNetworkLength network) $ \expected ->+ let required+ | network == AddrTorV2 = Nothing+ | otherwise = addressNetworkLength network+ forM_ required $ \expected -> unless (B.length address == expected) $ fail $ unwords@@ -240,8 +254,10 @@ deriving (Eq, Show, Generic) deriving newtype (NFData) +-- | A message with more than 'maxAddrSize' entries fails the parse, as BIP+-- 155 asks ("Clients SHOULD reject messages with more addresses"). instance Serial AddrV2 where- deserialize = AddrV2 <$> getVarIntList deserialize+ deserialize = AddrV2 <$> getVarIntListMax "addrv2" maxAddrSize deserialize serialize (AddrV2 xs) = putVarIntList serialize xs instance Binary AddrV2 where@@ -252,12 +268,20 @@ get = deserialize put = serialize --- | Prefix of an IPv4-mapped IPv6 address.+-- | Prefix of an IPv4-mapped IPv6 address, @::ffff:0:0/96@. ipv4Prefix :: ByteString ipv4Prefix = B.replicate 10 0 <> B.pack [0xff, 0xff] +-- | Prefix of a Tor v2 address wrapped in IPv6 by OnionCat,+-- @fd87:d87e:eb43::/48@.+onionCatPrefix :: ByteString+onionCatPrefix = B.pack [0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43]+ -- | The 'addrv2' form of an 'addr' entry. An IPv4-mapped address becomes an--- 'AddrIPv4' entry with four address bytes. Any other address is IPv6.+-- 'AddrIPv4' entry with four address bytes, and an OnionCat address an+-- 'AddrTorV2' entry with ten, since BIP 155 forbids sending either under the+-- IPv6 network. Any other address is IPv6. Tor v2 is no longer in use, and+-- BIP 155 says such entries must not be relayed. toNetworkAddressV2 :: NetworkAddressTime -> NetworkAddressV2 toNetworkAddressV2 (t, NetworkAddress s (HostAddress bs)) = NetworkAddressV2 {time = t, services = s, network, address, port}@@ -268,15 +292,26 @@ .|. fromIntegral (B.index bs 17) (network, address) | B.take 12 ip == ipv4Prefix = (AddrIPv4, B.drop 12 ip)+ | B.take 6 ip == onionCatPrefix = (AddrTorV2, B.drop 6 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.+-- one. Tor, I2P and CJDNS addresses do not fit the 'addr' message. An IPv6+-- entry that holds an IPv4-mapped or an OnionCat address has none either:+-- BIP 155 says to ignore those on receive, and Bitcoin Core reads them as+-- invalid addresses, because the same peer would otherwise be known under+-- two networks. An entry whose address does not have the length of its+-- network has no 'addr' form. fromNetworkAddressV2 :: NetworkAddressV2 -> Maybe NetworkAddressTime fromNetworkAddressV2 a = case a.network of- AddrIPv4 -> Just (entry (ipv4Prefix <> a.address))- AddrIPv6 -> Just (entry a.address)+ AddrIPv4+ | B.length a.address == 4 -> Just (entry (ipv4Prefix <> a.address))+ AddrIPv6+ | B.length a.address == 16+ && B.take 12 a.address /= ipv4Prefix+ && B.take 6 a.address /= onionCatPrefix ->+ Just (entry a.address) _ -> Nothing where entry ip = (a.time, NetworkAddress a.services (HostAddress (ip <> portBytes)))@@ -340,8 +375,9 @@ deriving (Eq, Show, Generic) deriving newtype (NFData) +-- | A message with more than 'maxInvSize' entries fails the parse. instance Serial GetData where- deserialize = GetData <$> getVarIntList deserialize+ deserialize = GetData <$> getVarIntListMax "getdata" maxInvSize deserialize serialize (GetData xs) = do putVarInt $ length xs@@ -365,8 +401,9 @@ deriving (Eq, Show, Generic) deriving newtype (NFData) +-- | A message with more than 'maxInvSize' entries fails the parse. instance Serial Inv where- deserialize = Inv <$> getVarIntList deserialize+ deserialize = Inv <$> getVarIntListMax "inv" maxInvSize deserialize serialize (Inv xs) = do putVarInt $ length xs@@ -496,12 +533,17 @@ } deriving (Eq, Show, Generic, NFData) +-- | The socket address of a 'HostAddress': IPv4 for an IPv4-mapped address,+-- IPv6 otherwise. hostToSockAddr :: HostAddress -> SockAddr hostToSockAddr (HostAddress bs) = case runGetS getSockAddr bs of Left e -> error e Right x -> x +-- | The 'HostAddress' of an IPv4 or IPv6 socket address. Any other kind of+-- socket address, such as a Unix socket, is an error: check the address+-- first when it does not come from a TCP connection. sockToHostAddress :: SockAddr -> HostAddress sockToHostAddress = HostAddress . runPutS . putSockAddr @@ -802,8 +844,53 @@ VarInt count <- deserialize when (count > maxListSize) $ fail ("List count too large: " ++ show count)- replicateM (fromIntegral count) element+ getElements count element +-- | Read the given number of elements, in a loop that keeps nothing but the+-- elements read so far.+getElements :: (MonadGet m) => Word64 -> m a -> m [a]+getElements count element = go count []+ where+ go 0 acc = return (reverse acc)+ go n acc = do+ x <- element+ go (n - 1) (x : acc)++-- | Read a list like 'getVarIntList', and fail when its count is above the+-- given limit. The count is checked right after it is read, before any+-- element. The name is that of the message, for the error text.+getVarIntListMax :: (MonadGet m) => String -> Word64 -> m a -> m [a]+getVarIntListMax name limit element = do+ VarInt count <- deserialize+ when (count > limit) $+ fail (name ++ ": too many entries: " ++ show count ++ ", the limit is " ++ show limit)+ getElements count element++-- | Largest number of entries in an @inv@ or a @getdata@ message. Bitcoin+-- Core and Bitcoin Cash Node punish a peer that sends more (@MAX_INV_SZ@).+maxInvSize :: Word64+maxInvSize = 50000++-- | Largest number of entries in an @addr@ or an @addrv2@ message+-- (@MAX_ADDR_TO_SEND@, and BIP 155).+maxAddrSize :: Word64+maxAddrSize = 1000++-- | Largest number of headers in a @headers@ message+-- (@MAX_HEADERS_RESULTS@).+maxHeadersSize :: Word64+maxHeadersSize = 2000++-- | Largest number of hashes in the locator of a @getheaders@ or a+-- @getblocks@ message (@MAX_LOCATOR_SZ@).+maxLocatorSize :: Word64+maxLocatorSize = 101++-- | Largest number of filter hashes in a @cfheaders@ message (BIP 157:+-- "FilterHashesLength MUST NOT be greater than 2,000").+maxFilterHashes :: Word64+maxFilterHashes = 2000+ -- | Write a list with its element count as a 'VarInt' in front. putVarIntList :: (MonadPut m) => (a -> m ()) -> [a] -> m () putVarIntList element xs = do@@ -811,13 +898,17 @@ mapM_ element xs -- | Read bytes that start with their count as a 'VarInt'. A count above--- 'maxListSize' fails the parse.+-- 'maxListSize' fails the parse. A count of zero gives the one shared empty+-- byte string, so that a long run of empty items, as a peer can put in a+-- witness, costs a list cell each and no more. getVarIntBytes :: (MonadGet m) => m ByteString getVarIntBytes = do VarInt count <- deserialize when (count > maxListSize) $ fail ("Byte count too large: " ++ show count)- getByteString (fromIntegral count)+ if count == 0+ then return B.empty+ else getByteString (fromIntegral count) -- | Write bytes with their count as a 'VarInt' in front. putVarIntBytes :: (MonadPut m) => ByteString -> m ()@@ -851,22 +942,27 @@ -- the first message it will send is a 'Version' message. The other node -- will similarly respond with it's own 'Version' message. data Version = Version- { -- | protocol version- version :: !Word32,+ { -- | protocol version. Signed, as @int nVersion@ in Bitcoin Core, so a+ -- peer that sends ffffffff has version -1 and not a huge one.+ version :: !Int32, -- | features supported by this connection services :: !Word64,- -- | unix timestamp- timestamp :: !Word64,+ -- | unix timestamp, signed as @int64_t nTime@ in Bitcoin Core+ timestamp :: !Int64, -- | network address of remote node addrRecv :: !NetworkAddress, -- | network address of sending node addrSend :: !NetworkAddress, -- | random nonce to detect connection to self nonce :: !Word64,- -- | user agent string+ -- | user agent string: up to 'maxUserAgentLength' bytes that the peer+ -- chose. They are not checked to be text. Bitcoin Core passes them+ -- through @SanitizeString@ before it logs or shows them, and a caller+ -- should do the same. userAgent :: !VarString,- -- | height of the last block in sending node- startHeight :: !Word32,+ -- | height of the last block in sending node, signed as+ -- @int starting_height@ in Bitcoin Core+ startHeight :: !Int32, -- | relay transactions flag (BIP-37) relay :: !Bool }@@ -878,14 +974,14 @@ instance Serial Version where deserialize = Version- <$> getWord32le- <*> getWord64le+ <$> (fromIntegral <$> getWord32le) <*> getWord64le+ <*> (fromIntegral <$> getWord64le) <*> deserialize <*> deserialize <*> getWord64le <*> userAgent- <*> getWord32le+ <*> (fromIntegral <$> getWord32le) <*> (go =<< isEmpty) where go True = return True@@ -897,14 +993,14 @@ return ua serialize (Version v s t ar as n ua sh r) = do- putWord32le v+ putWord32le (fromIntegral v) putWord64le s- putWord64le t+ putWord64le (fromIntegral t) serialize ar serialize as putWord64le n serialize ua- putWord32le sh+ putWord32le (fromIntegral sh) putBool r instance Binary Version where@@ -975,10 +1071,10 @@ instance Show MessageCommand where showsPrec _ = shows . commandToString +-- | Reads what 'Show' writes, for any bytes: the name as a byte string+-- literal. A 'Reject' message can name a command with bytes outside ASCII. instance Read MessageCommand where- readPrec = do- String str <- lexP- return (stringToCommand (cs str))+ readPrec = stringToCommand <$> readPrec -- | 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
src/Haskoin/Network/CompactBlock.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-}@@ -21,6 +22,9 @@ GetBlockTxn (..), BlockTxn (..), maxShortId,+ getCompactBlock,+ getGetBlockTxn,+ getBlockTxn, ) where @@ -36,6 +40,7 @@ import GHC.Generics (Generic) import Haskoin.Block.Common (BlockHash, BlockHeader) import Haskoin.Network.Common+import Haskoin.Network.Data (Network (..), getNetworkTx) import Haskoin.Transaction.Common (Tx) -- | The @sendcmpct@ message. It tells the peer whether to announce new blocks@@ -106,13 +111,11 @@ } deriving (Eq, Show, Generic, NFData) +-- | This form knows no network, so it takes positions of up to 32 bits and+-- reads transactions in the extended format. Use 'getCompactBlock' for a+-- message from a peer. instance Serial CompactBlock where- deserialize =- CompactBlock- <$> deserialize- <*> getWord64le- <*> getVarIntList getShortId- <*> getDifferentialList (\i -> PrefilledTx i <$> deserialize)+ deserialize = compactBlockWith maxBound deserialize serialize c = do serialize c.header@@ -139,8 +142,10 @@ } deriving (Eq, Show, Generic, NFData) +-- | This form knows no network, so it takes positions of up to 32 bits. Use+-- 'getGetBlockTxn' for a message from a peer. instance Serial GetBlockTxn where- deserialize = GetBlockTxn <$> deserialize <*> getDifferentialList return+ deserialize = GetBlockTxn <$> deserialize <*> getDifferentialList maxBound return serialize g = do serialize g.hash putDifferentialList id (const (return ())) g.indexes@@ -176,26 +181,70 @@ put = serialize get = deserialize +-- | Read a @cmpctblock@ message of a network. On Bitcoin a position above+-- 65535 fails the parse, and so does a block of more than 65535+-- transactions, as in Bitcoin Core ("indexes overflowed 16 bits"). On+-- Bitcoin Cash positions have 32 bits, as in Bitcoin Cash Node, and the+-- transactions are read in the old format.+getCompactBlock :: (MonadGet m) => Network -> m CompactBlock+getCompactBlock net = compactBlockWith net.maxCompactBlockIndex (getNetworkTx net)++compactBlockWith :: (MonadGet m) => Word32 -> m Tx -> m CompactBlock+compactBlockWith maxIndex getTx = do+ header <- deserialize+ nonce <- getWord64le+ shortIds <- getVarIntList getShortId+ prefilled <- getDifferentialList maxIndex (\i -> PrefilledTx i <$> getTx)+ -- The positions of a block of n transactions are 0 to n - 1, and Bitcoin+ -- Core refuses a count above the largest value of its position type.+ when (toInteger (length shortIds) + toInteger (length prefilled) > toInteger maxIndex) $+ fail ("Compact block: indexes overflowed " ++ show (indexBits maxIndex) ++ " bits")+ return (CompactBlock header nonce shortIds prefilled)++-- | Read a @getblocktxn@ message of a network. On Bitcoin a position above+-- 65535 fails the parse, as in Bitcoin Core ("differential value overflow").+getGetBlockTxn :: (MonadGet m) => Network -> m GetBlockTxn+getGetBlockTxn net =+ GetBlockTxn <$> deserialize <*> getDifferentialList net.maxCompactBlockIndex return++-- | Read a @blocktxn@ message of a network. On a network without SegWit the+-- transactions are read in the old format, see 'getNetworkTx'.+getBlockTxn :: (MonadGet m) => Network -> m BlockTxn+getBlockTxn net = BlockTxn <$> deserialize <*> getVarIntList (getNetworkTx net)++-- | Width of the position type that holds this largest position.+indexBits :: Word32 -> Int+indexBits maxIndex+ | maxIndex <= 0xffff = 16+ | otherwise = 32+ -- | 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+-- previous position minus one. A position above the given largest one fails+-- the parse. Positions are strict numbers, so a long list costs no more than+-- its cells.+getDifferentialList :: (MonadGet m) => Word32 -> (Word32 -> m a) -> m [a]+getDifferentialList maxIndex element = do VarInt count <- deserialize when (count > maxListSize) $ fail ("List count too large: " ++ show count)- go count Nothing+ -- Positions increase strictly, so there are at most maxIndex + 1 of them.+ when (count > limit + 1) $ tooFar count+ go count Nothing [] where- go 0 _ = return []- go n previous = do+ limit = fromIntegral maxIndex :: Word64+ tooFar x =+ fail ("Block position does not fit " ++ show (indexBits maxIndex) ++ " bits: " ++ show x)+ go 0 _ acc = return (reverse acc)+ go n previous acc = 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+ -- Checked first, so that the sum below cannot wrap around.+ when (distance > limit) $ tooFar distance+ let position = distance + maybe 0 (\p -> fromIntegral p + 1) previous+ when (position > limit) $ tooFar position+ let !i = fromIntegral position :: Word32 x <- element i- (x :) <$> go (n - 1) (Just i)+ go (n - 1) (Just i) (x : acc) -- | Write a list whose elements each carry a block position, in the encoding -- that 'getDifferentialList' reads. Positions must be strictly increasing,
src/Haskoin/Network/Constants.hs view
@@ -84,6 +84,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 1000000, maxMessageSize = 4000000,+ maxProtocolMessageSize = 4000000,+ maxCompactBlockIndex = 0xffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-btc:" <> versionString <> "/",@@ -98,6 +100,7 @@ bip65Height = 388381, bip66Height = 363725, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0x0145ec036acc5ba740052af1a0, targetSpacing = 10 * 60, checkpoints = [ ( 11111,@@ -185,6 +188,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 1000000, maxMessageSize = 4000000,+ maxProtocolMessageSize = 4000000,+ maxCompactBlockIndex = 0xffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-btc-test:" <> versionString <> "/", defaultPort = 18333,@@ -198,6 +203,7 @@ bip65Height = 581885, bip66Height = 330776, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0x17f49f702147f10c0eb6, targetSpacing = 10 * 60, checkpoints = [ ( 546,@@ -246,6 +252,8 @@ genesisTx = testnet4GenesisTx, maxBlockSize = 1000000, maxMessageSize = 4000000,+ maxProtocolMessageSize = 4000000,+ maxCompactBlockIndex = 0xffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-btc-test4:" <> versionString <> "/", defaultPort = 48333,@@ -255,10 +263,12 @@ powLimit = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff, bip34Height = 1,- bip34Hash = Just "0000000012982b6d5f621229286b880e909984df669c2afabb102ce311b13f28",+ -- Bitcoin Core pins no block here: BIP34Hash is uint256{} on testnet4+ bip34Hash = Nothing, bip65Height = 1, bip66Height = 1, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0x0e346a558455ade8eca9, targetSpacing = 10 * 60, checkpoints = [], seeds =@@ -301,6 +311,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 1000000, maxMessageSize = 4000000,+ maxProtocolMessageSize = 4000000,+ maxCompactBlockIndex = 0xffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-btc-regtest:" <> versionString <> "/", defaultPort = 18444,@@ -313,7 +325,9 @@ bip34Hash = Nothing, bip65Height = 1, bip66Height = 1,- targetTimespan = 14 * 24 * 60 * 60,+ -- one day, as in Bitcoin Core (kernel/chainparams.cpp, CRegTestParams)+ targetTimespan = 24 * 60 * 60,+ minimumChainWork = 0, targetSpacing = 10 * 60, checkpoints = [], seeds = ["localhost"],@@ -353,6 +367,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 32000000, maxMessageSize = 64000000,+ maxProtocolMessageSize = 2097152,+ maxCompactBlockIndex = 0xffffffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-bch:" <> versionString <> "/", defaultPort = 8333,@@ -366,6 +382,7 @@ bip65Height = 388381, bip66Height = 363725, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0x030fd678912166474fe93f14, targetSpacing = 10 * 60, checkpoints = [ ( 11111,@@ -472,6 +489,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 2000000, maxMessageSize = 4000000,+ maxProtocolMessageSize = 2097152,+ maxCompactBlockIndex = 0xffffffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-bch-test4:" <> versionString <> "/", defaultPort = 28333,@@ -485,6 +504,7 @@ bip65Height = 3, bip66Height = 4, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0xbd5a1b5bda45b5da, targetSpacing = 10 * 60, checkpoints = [ ( 5000,@@ -553,6 +573,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 32000000, maxMessageSize = 64000000,+ maxProtocolMessageSize = 2097152,+ maxCompactBlockIndex = 0xffffffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-bch-test:" <> versionString <> "/", defaultPort = 18333,@@ -566,6 +588,7 @@ bip65Height = 581885, bip66Height = 330776, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0x011fdbbe582349040762, targetSpacing = 10 * 60, checkpoints = [ ( 546,@@ -632,6 +655,8 @@ genesisTx = mainnetGenesisTx, maxBlockSize = 32000000, maxMessageSize = 64000000,+ maxProtocolMessageSize = 2097152,+ maxCompactBlockIndex = 0xffffffff, maxSatoshi = 2100000000000000, userAgent = "/haskoin-bch-regtest:" <> versionString <> "/", defaultPort = 18444,@@ -645,6 +670,7 @@ bip65Height = 1351, bip66Height = 1251, targetTimespan = 14 * 24 * 60 * 60,+ minimumChainWork = 0, targetSpacing = 10 * 60, checkpoints = [ ( 0,
src/Haskoin/Network/Data.hs view
@@ -1,21 +1,25 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE NoFieldSelectors #-} module Haskoin.Network.Data ( Network (..),+ getNetworkTx, ) where import Control.DeepSeq import Data.Binary (Binary (..)) import Data.ByteString (ByteString)+import Data.Bytes.Get (MonadGet)+import Data.Bytes.Serial (deserialize) import Data.Serialize (Serialize (..)) import Data.Text (Text) import Data.Word (Word32, Word64, Word8) import GHC.Generics (Generic) import Haskoin.Block.Common-import Haskoin.Transaction.Common (Tx)+import Haskoin.Transaction.Common (Tx, parseLegacyTx) -- | Network definition. data Network = Network@@ -39,10 +43,22 @@ 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.+ -- | largest P2P message of any kind, in bytes. A message that carries+ -- block content (@block@, @cmpctblock@ or @blocktxn@) may be this large.+ -- Bitcoin Core refuses any message over 4,000,000 bytes+ -- (@MAX_PROTOCOL_MESSAGE_LENGTH@). Bitcoin Cash Node refuses those three+ -- messages over twice the block size. Use+ -- 'Haskoin.Network.Message.maxPayloadSize' for the limit of one command. maxMessageSize :: !Word32,+ -- | largest P2P message that carries no block content, in bytes. This is+ -- @MAX_PROTOCOL_MESSAGE_LENGTH@ of the reference node: 4,000,000 on+ -- Bitcoin, where it is the limit of every message, and 2 MiB on Bitcoin+ -- Cash, where @CMessageHeader::IsOversized@ applies it to every message+ -- but @block@, @cmpctblock@ and @blocktxn@.+ maxProtocolMessageSize :: !Word32,+ -- | largest transaction position that a compact block message may name.+ -- BIP 152 and Bitcoin Core use 16 bits (65535). Bitcoin Cash Node uses 32.+ maxCompactBlockIndex :: !Word32, -- | maximum amount of satoshi maxSatoshi :: !Word64, -- | user agent string@@ -72,6 +88,11 @@ targetSpacing :: !Word32, -- | checkpoints checkpoints :: ![(BlockHeight, BlockHash)],+ -- | work that the best chain is known to have, @nMinimumChainWork@ of the+ -- reference node. Once the best block has this much work,+ -- 'Haskoin.Block.Headers.connectBlocks' refuses a new header with less,+ -- which can only be a fork from far below the tip. Zero on regtest.+ minimumChainWork :: !Integer, -- | BIP44 derivation path root bip44Coin :: !Word32, -- | peer-to-peer network seeds@@ -101,3 +122,16 @@ halvingInterval :: !Word32 } deriving (Eq, Show, Read, Generic, NFData)++-- | Read a transaction as the reference node of the network reads one that+-- a peer sends. On a network with SegWit this is the extended format of BIP+-- 144, with its marker, flag and witness. On a network without SegWit it is+-- the old format only: Bitcoin Cash Node has no witness format, so it reads+-- the marker byte as an input count of zero. Reading the old format there+-- also means that a peer cannot make a Bitcoin Cash node build witness+-- stacks.+getNetworkTx :: (MonadGet m) => Network -> m Tx+getNetworkTx net+ | net.segWit = deserialize+ | otherwise = parseLegacyTx+
src/Haskoin/Network/Message.hs view
@@ -21,6 +21,7 @@ msgType, putMessage, getMessage,+ maxPayloadSize, ) where @@ -170,34 +171,53 @@ msgType (MSendTxRcncl _) = MCSendTxRcncl msgType (MOther c _) = MCOther c --- | 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.+-- | Largest payload that a message of this command may have on a network.+-- On Bitcoin every message is limited to 4,000,000 bytes, as in Bitcoin Core.+-- On Bitcoin Cash a message is limited to 2 MiB, unless it carries block+-- content: @block@, @cmpctblock@ and @blocktxn@ may be twice the block size.+-- This is @CMessageHeader::IsOversized@ in Bitcoin Cash Node. A node should+-- check the length of a message header against this limit before it buffers+-- the payload.+maxPayloadSize :: Network -> MessageCommand -> Word32+maxPayloadSize net cmd+ | carriesBlock = net.maxMessageSize+ | otherwise = min net.maxMessageSize net.maxProtocolMessageSize+ where+ carriesBlock = cmd `elem` [MCBlock, MCCmpctBlock, MCBlockTxn]++-- | Read one message of a network. The checks come in this order: the magic+-- bytes, the command name, the length against 'maxPayloadSize' of the+-- command, then the payload is read and its checksum compared. The payload+-- parser sees the declared bytes only. Any failure fails the parse. Bitcoin+-- Core drops a message with a wrong checksum or an invalid command name and+-- keeps the connection, which a caller can do by reading the header itself.+--+-- Transactions, in @tx@, @block@, @cmpctblock@ and @blocktxn@ messages, are+-- read with 'getNetworkTx', and compact block positions are limited as the+-- network requires, see 'getCompactBlock' and 'getGetBlockTxn'. getMessage :: (MonadGet m) => Network -> m Message getMessage net = do- (MessageHeader mgc cmd len chk) <- deserialize+ -- The magic is checked before the rest of the header is looked at.+ mgc <- lookAhead getWord32be unless (mgc == net.magic) (fail $ "get: Invalid network magic bytes: " ++ show mgc)- when (len > net.maxMessageSize) $+ (MessageHeader _ cmd len chk) <- deserialize+ let limit = maxPayloadSize net cmd+ when (len > limit) $ fail $ "get: Message of " ++ show len ++ " bytes is larger than the limit of "- ++ show net.maxMessageSize+ ++ show limit payload <- getByteString (fromIntegral len) unless (checkSum32 payload == chk) (fail $ "get: Invalid message checksum: " ++ show chk)- either fail return (runGetS (getPayload cmd len) payload)+ either fail return (runGetS (getPayload net cmd len) payload) --- | Parser for the payload of a message of the given command.-getPayload :: (MonadGet m) => MessageCommand -> Word32 -> m Message-getPayload cmd len =+getPayload :: (MonadGet m) => Network -> MessageCommand -> Word32 -> m Message+getPayload net cmd len = case cmd of MCVersion -> MVersion <$> deserialize MCVerAck -> return MVerAck@@ -209,8 +229,8 @@ MCNotFound -> MNotFound <$> deserialize MCGetBlocks -> MGetBlocks <$> deserialize MCGetHeaders -> MGetHeaders <$> deserialize- MCTx -> MTx <$> deserialize- MCBlock -> MBlock <$> deserialize+ MCTx -> MTx <$> getNetworkTx net+ MCBlock -> MBlock <$> (Block <$> deserialize <*> getVarIntList (getNetworkTx net)) MCMerkleBlock -> MMerkleBlock <$> deserialize MCHeaders -> MHeaders <$> deserialize MCGetAddr -> return MGetAddr@@ -225,9 +245,9 @@ MCSendHeaders -> return MSendHeaders MCFeeFilter -> MFeeFilter <$> deserialize MCSendCmpct -> MSendCompact <$> deserialize- MCCmpctBlock -> MCompactBlock <$> deserialize- MCGetBlockTxn -> MGetBlockTxn <$> deserialize- MCBlockTxn -> MBlockTxn <$> deserialize+ MCCmpctBlock -> MCompactBlock <$> getCompactBlock net+ MCGetBlockTxn -> MGetBlockTxn <$> getGetBlockTxn net+ MCBlockTxn -> MBlockTxn <$> getBlockTxn net MCGetCFilters -> MGetCFilters <$> deserialize MCCFilter -> MCFilter <$> deserialize MCGetCFHeaders -> MGetCFHeaders <$> deserialize
src/Haskoin/Script/Common.hs view
@@ -114,6 +114,14 @@ -- 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.+--+-- 'OP_INVALIDOPCODE' holds a byte that has no opcode of its own. The parser+-- only builds it for such a byte. Built by hand with the byte of a defined+-- opcode, such as 0xac, it is a second value for that opcode: it is not equal+-- to 'OP_CHECKSIG', it serializes to the same byte, and it reads back as+-- 'OP_CHECKSIG'. Code that looks for an opcode, such as the removal of+-- 'OP_CODESEPARATOR' in 'Haskoin.Script.SigHash.txSigHash', does not see it.+-- Do not build one for a byte that has a constructor. data ScriptOp = -- Pushing Data OP_PUSHDATA
src/Haskoin/Script/SigHash.hs view
@@ -83,12 +83,18 @@ SIGHASH_FORKID | -- | new inputs can be added SIGHASH_ANYONECANPAY- deriving (Eq, Ord, Show, Read, Generic)+ deriving (Eq, Ord, Show, Read, Generic, Bounded) instance NFData SigHashFlag instance Hashable SigHashFlag +-- | 'fromEnum' gives the value of the flag in a hash type byte, and 'toEnum'+-- reads it back. The values are not consecutive, so the functions that list+-- flags walk the constructors in their order instead of counting: @[minBound+-- ..]@ is the five flags. 'toEnum' of a number that is no flag, 'succ' of the+-- last flag and 'pred' of the first raise an error, as they do for any+-- enumeration. instance Enum SigHashFlag where fromEnum SIGHASH_ALL = 0x01 fromEnum SIGHASH_NONE = 0x02@@ -101,7 +107,34 @@ toEnum 0x40 = SIGHASH_FORKID toEnum 0x80 = SIGHASH_ANYONECANPAY toEnum _ = error "Not a valid sighash flag"+ succ flag+ | flag == maxBound = error "succ: SIGHASH_ANYONECANPAY is the last sighash flag"+ | otherwise = sigHashFlags !! (flagPosition flag + 1)+ pred flag+ | flag == minBound = error "pred: SIGHASH_ALL is the first sighash flag"+ | otherwise = sigHashFlags !! (flagPosition flag - 1)+ enumFrom flag = enumFromTo flag maxBound+ enumFromTo from to = map (sigHashFlags !!) [flagPosition from .. flagPosition to]+ enumFromThen from next+ | next >= from = enumFromThenTo from next maxBound+ | otherwise = enumFromThenTo from next minBound+ enumFromThenTo from next to =+ map (sigHashFlags !!) [flagPosition from, flagPosition next .. flagPosition to] +-- | The flags in the order of their constructors.+sigHashFlags :: [SigHashFlag]+sigHashFlags =+ [ SIGHASH_ALL,+ SIGHASH_NONE,+ SIGHASH_SINGLE,+ SIGHASH_FORKID,+ SIGHASH_ANYONECANPAY+ ]++-- | The position of a flag in 'sigHashFlags'.+flagPosition :: SigHashFlag -> Int+flagPosition flag = length (takeWhile (/= flag) sigHashFlags)+ -- | Data type representing the different ways a transaction can be signed. -- When producing a signature, a hash of the transaction is used as the message -- to be signed. The 'SigHash' parameter controls which parts of the@@ -214,8 +247,19 @@ -- 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.+-- @SerializeScriptCode@ does in Bitcoin Core. --+-- Bitcoin Core makes a second change to the script before it hashes a legacy+-- input: @FindAndDelete@ removes every push of the signature being checked.+-- This function does not do that. The standard output scripts never hold a+-- signature, so nothing is lost for them. A caller that verifies another+-- legacy script must remove the pushes of the signature from the script+-- first.+--+-- The hash type is written as four bytes, as Bitcoin Core writes the+-- @nHashType@ it is given. A signature only carries the low byte, so a+-- 'SigHash' above 0xff gives a digest that no signature can match.+-- -- 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@@ -298,6 +342,27 @@ -- executed before the signature check. Unlike 'txSigHash', nothing is removed -- from that part, as BIP 143 specifies. --+-- The last field of the digest is the hash type as four bytes. On a network+-- without a fork id it is the 'SigHash' as given, which is what+-- @SignatureHash@ of Bitcoin Core writes. A signature only carries the low+-- byte, so a 'SigHash' above 0xff gives a digest that no signature can+-- match. On a network with a fork id it is the low byte with the fork id of+-- the network in the three bytes above it.+--+-- What is not supported on Bitcoin Cash, where Bitcoin Cash Node is the+-- reference:+--+-- * @SIGHASH_UTXOS@ (0x20), which adds a hash of all the coins spent to the+-- digest. 'isSigHashUnknown' treats the bit as undefined, so 'decodeTxSig'+-- refuses such a signature.+--+-- * Coins that hold tokens. Their digest also covers the token data, and+-- their output script starts with the prefix byte 0xef, which+-- 'Haskoin.Script.Standard.decodeOutput' does not read. A signature made+-- here for such a coin is not valid.+--+-- * Schnorr signatures, see 'TxSignature'.+-- -- Fails when the input index is not an index of the transaction. txSigHashForkId :: Network ->@@ -326,8 +391,11 @@ putWord32le (tx.inputs !! i).sequence serialize hashOutputs putWord32le tx.locktime- putWord32le $ fromIntegral $ sigHashAddNetworkId net sh+ putWord32le $ fromIntegral hashType where+ hashType = case net.sigHashForkId of+ Just forkId -> sigHashAddForkId sh forkId+ Nothing -> sh hashPrevouts | not (anyoneCanPay sh) = doubleSHA256 . runPutS $ mapM_ (serialize . (.outpoint)) tx.inputs@@ -353,13 +421,34 @@ -- 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.+-- This is the form of a signature that nodes relay. The binary and JSON+-- forms, and 'decodeTxSig', apply the three signature rules of the standard+-- script flags of Bitcoin Core, and one rule of the network:+--+-- * @DERSIG@: strict DER, as BIP 66 defines it, with r and s between 1 and+-- the curve order minus 1.+--+-- * @LOW_S@: s is at most half the curve order.+--+-- * @STRICTENC@: the hash type byte is defined ('isSigHashUnknown').+--+-- * The 'SIGHASH_FORKID' flag is set exactly on the networks that have a+-- fork id.+--+-- Nothing may follow the hash type byte.+--+-- Only the first rule is a consensus rule, and only since BIP 66. A block+-- can therefore hold signatures that do not decode: one with a high S, one+-- with an undefined hash type byte, and, before BIP 66 activated, one in a+-- looser DER form. An input with such a signature fails+-- 'Haskoin.Script.Standard.decodeInput', and has no+-- 'Haskoin.Address.inputAddress'. This type is not a parser for the whole+-- chain history. 'Haskoin.Crypto.Signature.decodeStrictSig' reads a+-- signature under BIP 66 alone.+--+-- On Bitcoin Cash, a signature of 64 bytes followed by the hash type byte is+-- a Schnorr signature, which Bitcoin Cash Node accepts in @OP_CHECKSIG@.+-- This type only holds ECDSA signatures, so those do not decode either. data TxSignature = TxSignature { sig :: !Sig,@@ -380,6 +469,8 @@ empty = return TxSignatureEmpty decode = do sig <- marshalGet ctx+ unless (isCanonicalHalfOrder ctx sig) $+ fail "Non-canonical signature: S value is unnecessarily high" sh <- fromIntegral <$> getWord8 when (isSigHashUnknown sh) $ fail "Non-canonical signature: unknown hashtype byte"@@ -406,7 +497,9 @@ 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'.+-- 'TxSignature': strict DER, a low S, a defined hash type, and the fork id+-- flag that the network asks for. The empty string is 'TxSignatureEmpty'. A+-- signature that is valid by consensus alone does not decode. decodeTxSig :: Network -> Ctx -> ByteString -> Either String TxSignature decodeTxSig net ctx = runGetS $ do
src/Haskoin/Script/Standard.hs view
@@ -33,10 +33,14 @@ isPayScript32Hash, isDataCarrier, maxMultisigKeys,+ maxRedeemScriptSize,+ maxWitnessScriptSize, encodeOutput, decodeOutput, toP2SH, toP2WSH,+ p2shScriptHash,+ p2wshScriptHash, sortMulSig, -- * Standard Script Inputs@@ -54,7 +58,7 @@ import Control.Applicative ((<|>)) import Control.DeepSeq-import Control.Monad (unless)+import Control.Monad (guard, unless) import Crypto.Secp256k1 import Data.Aeson (ToJSON (..), Value (..), withText) import Data.Aeson.Encoding (Encoding, text)@@ -165,9 +169,31 @@ -- | The most keys a multisig output can have: @MAX_PUBKEYS_PER_MULTISIG@ of -- Bitcoin Core.+--+-- Twenty keys fit in a bare multisig output and in a pay-to-witness-script-hash+-- output. They do not fit in a pay-to-script-hash output, whose redeem script+-- is limited to 'maxRedeemScriptSize' bytes. That is 15 compressed keys+-- (3 + 15 × 34 = 513 bytes) or 7 uncompressed keys (3 + 7 × 66 = 465 bytes). maxMultisigKeys :: Int maxMultisigKeys = 20 +-- | The largest redeem script a pay-to-script-hash output can have, in bytes.+-- The redeem script is one push in the input script, and consensus limits a+-- push to 520 bytes: @MAX_SCRIPT_ELEMENT_SIZE@ of Bitcoin Core. Coins sent to+-- the hash of a larger script can never be spent.+maxRedeemScriptSize :: Int+maxRedeemScriptSize = 520++-- | The largest witness script a pay-to-witness-script-hash output can have,+-- in bytes: @MAX_SCRIPT_SIZE@ of Bitcoin Core, 10,000. The interpreter fails+-- on a larger script, so coins sent to its hash can never be spent.+--+-- This is the consensus limit. The relay policy of Bitcoin Core is stricter:+-- it only relays a spend whose witness script is at most 3,600 bytes+-- (@MAX_STANDARD_P2WSH_SCRIPT_SIZE@). This library does not enforce that.+maxWitnessScriptSize :: Int+maxWitnessScriptSize = 10000+ -- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the -- script is not recognized as any of the standard output types. --@@ -179,6 +205,33 @@ -- 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.+--+-- A 'ScriptOutput' describes what a script does, not its bytes. Two kinds of+-- script decode to a value that 'encodeOutput' writes back as other bytes:+-- a multisig whose keys are not direct pushes, and a data carrier whose data+-- is not pushed in the shortest way. Bitcoin Core gives those the same type+-- (@multisig@ and @nulldata@). So never hash or sign the result of+-- @encodeOutput@ in place of a script that came from somewhere else. Hash+-- and sign the bytes of that script. 'decodeInput' refuses a redeem script+-- that would not come back as the same bytes.+--+-- Three kinds of script that the @Solver@ of Bitcoin Core classifies are not+-- decoded here, because a 'PublicKey' holds a point of the curve:+--+-- * A pay-to-public-key output, or a multisig output, with a key of the+-- hybrid form (first byte 0x06 or 0x07). The @Solver@ only checks the size+-- and the first byte of a key. Such keys are valid by consensus and refused+-- by the relay policy.+--+-- * The same outputs with a key of the right size that is not a point of the+-- curve. Protocols that store data in bare multisig outputs make those.+--+-- * @OP_RETURN@ followed by @OP_RESERVED@, which the push-only test of+-- Bitcoin Core counts as a push.+--+-- On Bitcoin Cash, an output that holds tokens starts with the token prefix+-- byte 0xef and is not decoded. See 'Haskoin.Script.SigHash.txSigHashForkId'+-- for what is not supported on that network. decodeOutput :: Ctx -> Script -> Either String ScriptOutput decodeOutput ctx s = case s.ops of -- Pay to PubKey@@ -301,14 +354,49 @@ Right o -> return o marshalPut ctx = either error serialize . encodeOutput ctx --- | Encode script as pay-to-script-hash script-toP2SH :: Script -> ScriptOutput-toP2SH = PayScriptHash . addressHash . runPutS . serialize+-- | The pay-to-script-hash output of a redeem script. Fails for a script+-- longer than 'maxRedeemScriptSize' bytes, which could never be spent.+toP2SH :: Script -> Either String ScriptOutput+toP2SH = fmap PayScriptHash . p2shScriptHash --- | Encode script as a pay-to-witness-script-hash script-toP2WSH :: Script -> ScriptOutput-toP2WSH = PayWitnessScriptHash . sha256 . runPutS . serialize+-- | The pay-to-witness-script-hash output of a witness script. Fails for a+-- script longer than 'maxWitnessScriptSize' bytes, which could never be+-- spent.+toP2WSH :: Script -> Either String ScriptOutput+toP2WSH = fmap PayWitnessScriptHash . p2wshScriptHash +-- | The hash that a pay-to-script-hash output commits to: HASH160 of the+-- serialized redeem script. Fails for a script longer than+-- 'maxRedeemScriptSize' bytes.+p2shScriptHash :: Script -> Either String Hash160+p2shScriptHash script+ | size > maxRedeemScriptSize =+ Left $+ "The redeem script is "+ <> show size+ <> " bytes long, and a pay-to-script-hash redeem script can have at most "+ <> show maxRedeemScriptSize+ | otherwise = Right (addressHash bytes)+ where+ bytes = runPutS (serialize script)+ size = B.length bytes++-- | The hash that a pay-to-witness-script-hash output commits to: SHA256 of+-- the serialized witness script. Fails for a script longer than+-- 'maxWitnessScriptSize' bytes.+p2wshScriptHash :: Script -> Either String Hash256+p2wshScriptHash script+ | size > maxWitnessScriptSize =+ Left $+ "The witness script is "+ <> show size+ <> " bytes long, and a pay-to-witness-script-hash witness script can have at most "+ <> show maxWitnessScriptSize+ | otherwise = Right (sha256 bytes)+ where+ bytes = runPutS (serialize script)+ size = B.length bytes+ -- | 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@@ -478,6 +566,28 @@ -- the redeem script requires, see 'ScriptHashInput'. When the redeem script -- is a SegWit program and nothing comes before it, the input is a -- 'NestedWitnessInput'.+--+-- What this function reads is the standard form that nodes relay, not every+-- input that a block may hold:+--+-- * A signature must be a 'TxSignature': strict DER, a low S value and a+-- defined hash type. An input with a signature that is valid by consensus+-- alone does not decode. See 'TxSignature'.+--+-- * The redeem script must be in the form that 'encodeOutput' writes, byte+-- for byte. Bitcoin Core's @Solver@ also calls a script a multisig when a+-- key is pushed with @OP_PUSHDATA1@, but a 'ScriptOutput' cannot hold the+-- form of a push, so such a script would come back out as other bytes with+-- another hash. It is refused here, so that 'Haskoin.Address.inputAddress'+-- is always the hash of the bytes the input carries, and so that encoding+-- a decoded input never rewrites its redeem script. Spending such a script+-- breaks the @MINIMALDATA@ rule of the relay policy, so no standard+-- transaction has one.+--+-- * The witness program of a 'NestedWitnessInput' must be a direct push and+-- the only element of the script. BIP 141 makes any other form invalid by+-- consensus: "the scriptSig must be exactly a push of the BIP16+-- redeemScript". decodeInput :: Network -> Ctx -> Script -> Either String ScriptInput decodeInput net ctx s@(Script ops) = maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash@@ -486,15 +596,24 @@ RegularInput <$> eitherToMaybe (decodeSimpleInput net ctx s) matchPayScriptHash = case splitAt (length s.ops - 1) ops of- (is, [OP_PUSHDATA bs _]) -> do+ (is, [OP_PUSHDATA bs pushType]) -> do rdm <- eitherToMaybe $ unmarshal ctx bs+ guard (canonical rdm bs) case (is, rdm) of- ([], PayWitnessPKHash _) -> return $ NestedWitnessInput rdm- ([], PayWitnessScriptHash _) -> return $ NestedWitnessInput rdm+ ([], PayWitnessPKHash _) -> nested pushType rdm+ ([], PayWitnessScriptHash _) -> nested pushType rdm _ -> do inp <- decodeSimpleInputFor net ctx rdm is return $ ScriptHashInput inp rdm _ -> Nothing+ -- The redeem script, written back, gives the bytes that were pushed.+ canonical rdm bs =+ fmap (runPutS . serialize) (encodeOutput ctx rdm) == Right bs+ -- A witness program is 22 or 34 bytes, so its only valid push is the+ -- direct one.+ nested pushType rdm = do+ guard (pushType == OPCODE)+ return (NestedWitnessInput rdm) errMsg = "decodeInput: Could not decode script input" instance Marshal (Network, Ctx) ScriptInput where
src/Haskoin/Transaction/Builder.hs view
@@ -42,6 +42,7 @@ chooseCoinsSink, countMulSig, greedyAddSink,+ maxStandardTxWeight, -- * Size and fee estimation InputKind (..),@@ -51,18 +52,20 @@ guessTxFee, inputWeight, outputSize,+ dustThreshold, ) where -import Control.Monad (foldM, unless)+import Control.Monad (foldM, unless, when) import Control.Monad.Identity (runIdentity) import Crypto.Secp256k1 import Data.ByteString qualified as B import Data.Conduit (ConduitT, Void, await, runConduit, (.|)) import Data.Conduit.List (sourceList) import Data.Either (fromRight, rights)-import Data.List (find, nub)-import Data.Maybe (catMaybes, fromJust, isJust, mapMaybe)+import Data.List (nub)+import Data.Map.Strict qualified as Map+import Data.Maybe (catMaybes, isNothing) import Data.String.Conversions (cs) import Data.Text (Text) import Data.Word (Word64)@@ -86,10 +89,27 @@ coinValue :: c -> Word64 coinInputKind :: c -> InputKind +-- | The weight above which nodes do not relay a transaction: the+-- @MAX_STANDARD_TX_WEIGHT@ of Bitcoin Core (@policy/policy.h@). A Bitcoin+-- Cash transaction has no witness, so its weight is four times its size,+-- and the limit is the 100,000 bytes of @MAX_STANDARD_TX_SIZE@ in Bitcoin+-- Cash Node.+maxStandardTxWeight :: Int+maxStandardTxWeight = 400000+ -- | 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'.+-- is spent with and the kinds of the outputs, as 'guessTxFee' estimates it.+--+-- The coins are taken in the order of the list until they cover the target+-- and the fee. The selection fails when the coins do not suffice, when a+-- kind of input or output is not valid, and when the transaction would+-- weigh more than 'maxStandardTxWeight', which nodes do not relay.+--+-- The change can be any amount, including one below 'dustThreshold'. Nodes+-- do not relay a transaction with an output below that threshold, so leave+-- such a change to the fee instead of making an output for it. chooseCoins :: (Coin c) => -- | value to send@@ -111,7 +131,7 @@ -- | 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.+-- selection. See 'chooseCoins'. chooseCoinsSink :: (Monad m, Coin c) => -- | value to send@@ -125,20 +145,97 @@ -- | coin selection and change ConduitT c Void m (Either String ([c], Word64)) chooseCoinsSink target fee outputs continue- | target > 0 =- maybeToEither err- <$> greedyAddSink target feeOf continue- | otherwise = return $ Left "chooseCoins: Target must be > 0"+ | target == 0 = return $ Left "chooseCoins: Target must be > 0"+ | Left e <- mapM_ checkOutputKind outputs = return $ Left ("chooseCoins: " <> e)+ | otherwise = go emptySelection Nothing where- err = "chooseCoins: No solution found"- feeOf coins = guessTxFee fee (map coinInputKind coins) outputs+ outputBytes = varIntSize (length outputs) + sum (map outputSize outputs)+ -- What the coins of a selection must reach: the target and the fee of+ -- the transaction they make. Amounts are added as unbounded integers,+ -- so that no sum wraps around.+ goal sel = toInteger target + toInteger fee * toInteger ((selectionWeight outputBytes sel + 3) `div` 4)+ go sel previous =+ await >>= \case+ Nothing -> return $ maybe (Left "chooseCoins: No solution found") solution previous+ Just coin -> case addCoin coin sel of+ Left e -> return $ Left ("chooseCoins: " <> e)+ Right sel'+ -- A heavier transaction is not relayed. Coins only add weight,+ -- so nothing further in the stream can complete this selection.+ | selectionWeight outputBytes sel' > maxStandardTxWeight ->+ return $ maybe (Left tooHeavy) solution previous+ | sel'.total < goal sel' -> go sel' previous+ | not continue -> return (solution sel')+ -- This solution is the first one, or it takes less than the+ -- previous one: look for a better one in the rest of the stream+ | maybe True (\p -> sel'.total < p.total) previous -> go emptySelection (Just sel')+ | otherwise -> return $ maybe (Left "chooseCoins: No solution found") solution previous+ solution sel+ | change > toInteger (maxBound :: Word64) = Left "chooseCoins: The change does not fit in 64 bits"+ | otherwise = Right (sel.coins, fromInteger change)+ where+ change = sel.total - goal sel+ tooHeavy =+ "chooseCoins: The selection exceeds the standard transaction weight of " <> show maxStandardTxWeight +-- | The coins selected so far, with the sums that give the weight of the+-- transaction they make. The sums are kept as coins are added, so that+-- adding a coin does not walk over the coins before it.+data Selection c = Selection+ { -- | the coins, the last one added first+ coins :: [c],+ count :: !Int,+ -- | the sum of the values+ total :: !Integer,+ -- | the bytes of the inputs outside the witness+ baseBytes :: !Int,+ -- | the bytes of the witness stacks, an input without a witness+ -- counting the one byte of its empty stack+ stackBytes :: !Int,+ -- | whether an input has a witness+ hasStack :: !Bool+ }++emptySelection :: Selection c+emptySelection = Selection [] 0 0 0 0 False++addCoin :: (Coin c) => c -> Selection c -> Either String (Selection c)+addCoin coin sel = do+ let kind = coinInputKind coin+ checkInputKind kind+ let (base, stack) = inputWeight kind+ return+ Selection+ { coins = coin : sel.coins,+ count = sel.count + 1,+ total = sel.total + toInteger (coinValue coin),+ baseBytes = sel.baseBytes + base,+ stackBytes = sel.stackBytes + max 1 stack,+ hasStack = sel.hasStack || stack > 0+ }++-- | The weight of the transaction that spends the coins of a selection to+-- outputs of the size given, counted as 'guessTxWeight' counts it+selectionWeight :: Int -> Selection c -> Int+selectionWeight outputBytes sel = 4 * base + witness+ where+ base = 8 + varIntSize sel.count + sel.baseBytes + outputBytes+ witness+ | sel.hasStack = 2 + sel.stackBytes+ | otherwise = 0+ -- | 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 -- after a solution is found. If the next solution found is not strictly better -- than the previously found solution, the algorithm stops and returns the -- previous solution. If the continue flag is not set, the algorithm will return -- the first solution it finds in the stream.+--+-- The fee function is called on the coins taken so far each time a coin is+-- added. Amounts are added as unbounded integers, so that no sum wraps+-- around. The result is 'Nothing' when no selection covers the target and+-- its fee, or when the change does not fit in 64 bits. 'chooseCoinsSink'+-- does not use this function: it also bounds the weight of the selection. greedyAddSink :: (Monad m, Coin c) => -- | value to send@@ -150,50 +247,51 @@ -- | coin selection and change ConduitT c Void m (Maybe ([c], Word64)) greedyAddSink target guessFee continue =- go [] 0 [] 0+ go [] 0 Nothing where -- The goal is the value we must reach (including the fee) for a certain -- selection of coins.- goal coins = target + guessFee coins- go acc aTot ps pTot =+ goal coins = toInteger target + toInteger (guessFee coins)+ go acc aTot previous = 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 (coin : acc)- then -- If we want to continue searching for better solutions- if continue- then -- This solution is the first one or- -- This solution is better than the previous one- if pTot == 0 || val + aTot < pTot- then -- Continue searching for better solutions in the stream- go [] 0 (coin : acc) (val + aTot)- else -- Otherwise, we stop here and return the previous- -- solution- return $ Just (ps, pTot - goal ps)- else -- Otherwise, return this solution- return $- Just (coin : acc, val + aTot - goal (coin : acc))- else -- We have not yet reached the goal. Add the coin to the+ let tot = aTot + toInteger (coinValue coin)+ if tot < goal (coin : acc)+ then -- We have not yet reached the goal. Add the coin to the -- accumulator- go (coin : acc) (val + aTot) ps pTot+ go (coin : acc) tot previous+ else+ if not continue+ then return $ solution (coin : acc, tot)+ else -- This solution is the first one, or it is better than+ -- the previous one: continue searching for better solutions+ -- in the stream. Otherwise, stop here and return the+ -- previous solution.+ if maybe True ((tot <) . snd) previous+ then go [] 0 (Just (coin : acc, tot))+ else return $ solution =<< previous -- We reached the end of the stream- Nothing ->- return $- if null ps- then -- If no solution was found, return Nothing- Nothing- else -- If we have a solution, return it- Just (ps, pTot - goal ps)+ Nothing -> return $ solution =<< previous+ solution (coins, tot)+ | change > toInteger (maxBound :: Word64) = Nothing+ | otherwise = Just (coins, fromInteger change)+ where+ change = tot - goal coins {- Size and fee estimation -} -- | 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'.+-- unless the kind says otherwise. A multisig kind is @m@ of @n@, with @m@+-- from 1 to @n@. A SegWit multisig has up to 'maxMultisigKeys' keys. A+-- pay-to-script-hash multisig has up to 15: its redeem script is at most+-- 'maxRedeemScriptSize' bytes, and 15 compressed keys make 513. --+-- 'guessTxFee' and 'chooseCoins' refuse a kind outside these ranges.+-- 'inputWeight', 'guessTxWeight' and 'guessTxSize' do not check: on such a+-- kind their result means nothing.+-- -- 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.@@ -240,6 +338,28 @@ OutputScript Int deriving (Eq, Show, Read) +-- | Whether an input kind is one that can exist, see 'InputKind'+checkInputKind :: InputKind -> Either String ()+checkInputKind kind = case kind of+ InputMulSig m n -> multisig m n 15+ InputWitnessMulSig m n -> multisig m n maxMultisigKeys+ InputNestedWitnessMulSig m n -> multisig m n maxMultisigKeys+ _ -> Right ()+ where+ multisig m n most =+ unless (1 <= m && m <= n && n <= most) $+ Left $+ "The input kind " <> show kind <> " is not valid: m of n needs 1 <= m <= n <= " <> show most++-- | Whether an output kind is one that can exist: a script is from 0 to+-- 10,000 bytes long+checkOutputKind :: OutputKind -> Either String ()+checkOutputKind kind = case kind of+ OutputScript n+ | n < 0 || n > maxWitnessScriptSize ->+ Left $ "The output kind " <> show kind <> " is not valid: a script has from 0 to 10000 bytes"+ _ -> Right ()+ -- | 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@@ -264,15 +384,58 @@ -- | 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.+--+-- Bitcoin Core charges for signature operations as well: its size for fee+-- purposes is the larger of this size and 5 virtual bytes per signature+-- operation (@GetVirtualTransactionSize@ with @DEFAULT_BYTES_PER_SIGOP@ of+-- 20, in @policy/policy.cpp@). That only exceeds the size for transactions+-- dense in signature operations, such as one that pays to many bare+-- multisig outputs. This estimate, like 'txVirtualSize', leaves it out. guessTxSize :: [InputKind] -> [OutputKind] -> Int guessTxSize inputs outputs = (guessTxWeight inputs outputs + 3) `div` 4 -- | 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)+-- inputs and outputs of the transaction. Fails for a kind of input or+-- output that cannot exist, see 'InputKind', and for a fee that does not+-- fit in 64 bits.+guessTxFee :: Word64 -> [InputKind] -> [OutputKind] -> Either String Word64+guessTxFee feeRate inputs outputs = do+ mapM_ (prefixed . checkInputKind) inputs+ mapM_ (prefixed . checkOutputKind) outputs+ -- Multiplied as unbounded integers, so that the fee cannot wrap around+ let fee = toInteger feeRate * toInteger (guessTxSize inputs outputs)+ when (fee > toInteger (maxBound :: Word64)) $+ Left "guessTxFee: The fee does not fit in 64 bits"+ return (fromInteger fee)+ where+ prefixed = either (Left . ("guessTxFee: " <>)) Right +-- | The amount below which nodes do not relay an output of the kind, at+-- the default dust relay fee of Bitcoin Core, 3,000 satoshi per 1,000+-- virtual bytes: three satoshi for each byte of the output and of the+-- smallest input that spends it. This is @GetDustThreshold@ of Bitcoin+-- Core (@policy/policy.cpp@), which counts 148 bytes for the input of an+-- output without a witness and 67 for the input of a witness program. It+-- gives 546 satoshi for pay-to-public-key-hash, 294 for+-- pay-to-witness-public-key-hash, and 330 for pay-to-witness-script-hash+-- and Taproot. Bitcoin Cash Node gives the same amounts for the outputs+-- it has.+--+-- An 'OutputScript' is counted as an output without a witness. For a+-- witness program of another version this is more than Bitcoin Core asks,+-- which is the safe side. A data carrier output is never dust, whatever+-- this function says of its size.+dustThreshold :: OutputKind -> Word64+dustThreshold kind = 3 * fromIntegral (outputSize kind + spendSize)+ where+ spendSize = case kind of+ OutputWitnessPKHash -> witnessSpend+ OutputWitnessScriptHash -> witnessSpend+ OutputTaproot -> witnessSpend+ _ -> 32 + 4 + 1 + 107 + 4+ -- The script of the input counts a quarter in a witness+ witnessSpend = 32 + 4 + 1 + (107 `div` 4) + 4+ -- | 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,@@ -344,9 +507,22 @@ {- Build a new Tx -} -- | Build a transaction by providing a list of outpoints as inputs--- and a list of recipient addresses and amounts as outputs.+-- and a list of recipient addresses and amounts as outputs. The+-- transaction has version 1, no lock time, and final sequence numbers, so+-- it does not signal replacement (BIP 125).+--+-- Fails for an address that is not one of the network, and for an amount+-- above the 'maxSatoshi' of the network, as @createrawtransaction@ of+-- Bitcoin Core refuses an amount out of range. Like that call, it does not+-- check what needs the whole transaction: amounts that add up to more+-- than 'maxSatoshi', and an outpoint listed twice. Nodes refuse a+-- transaction with either, and so does 'verifyStdTx' for the second.+--+-- Nothing is added for change: what the outputs do not spend is the fee. buildAddrTx :: Network -> Ctx -> [OutPoint] -> [(Text, Word64)] -> Either String Tx-buildAddrTx net ctx ops rcps =+buildAddrTx net ctx ops rcps = do+ when (any ((> net.maxSatoshi) . snd) rcps) $+ Left $ "buildAddrTx: An amount is above the maximum of " <> show net.maxSatoshi <> " satoshi" buildTx ctx ops <$> mapM f rcps where f (aTxt, v) =@@ -356,7 +532,9 @@ return (o, v) -- | Build a transaction by providing a list of outpoints as inputs--- and a list of 'ScriptOutput' and amounts as outputs.+-- and a list of 'ScriptOutput' and amounts as outputs. It has no network+-- to check against, so it builds what it is given, whatever the amounts.+-- 'buildAddrTx' refuses an amount above the maximum of its network. buildTx :: Ctx -> [OutPoint] -> [(ScriptOutput, Word64)] -> Tx buildTx ctx ops rcpts = Tx 1 (toIn <$> ops) (toOut <$> rcpts) [] 0@@ -379,6 +557,20 @@ -- BIP 341 digest commits to the output every input spends, so every input -- of the transaction must have a 'SigInput' then. --+-- An input is signed with the keys of the list that its coin asks for. An+-- input whose coin asks for none of them is left as it is, so that several+-- parties can sign in turn. The result is then 'Right' with that input+-- unsigned: use 'verifyStdTx' to tell whether the transaction is complete.+--+-- The signer refuses, with the reason, what would make a transaction that+-- nodes do not accept: a hash type that is not defined, a redeem or a+-- witness script that does not hash to its coin or is too long, a SegWit+-- or Taproot coin on a network without SegWit, and a SegWit version 0 coin+-- whose key is uncompressed. See 'makeSignature' and 'sigKeys'. It signs+-- any defined hash type that a 'SigInput' asks for, 'sigHashNone' and+-- 'sigHashSingle' included, so check a 'SigInput' that comes from someone+-- else.+-- -- Example: P2WPKH -- -- > sigIn = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing@@ -446,6 +638,9 @@ -- | Merge partially-signed multisig transactions. This function does not -- support segwit and P2SH-segwit inputs. Use PSBTs to merge transactions with -- segwit inputs. An input that no transaction has signed stays unsigned.+-- The witness is not merged: the transactions must have the same one, and+-- the result keeps it, so that a SegWit input signed before the multisig+-- signers took their copies stays signed. mergeTxs :: Network -> Ctx ->@@ -469,11 +664,13 @@ 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+ Tx tx.version (ins tx.inputs i) tx.outputs tx.witness tx.locktime -- | Merge input from partially-signed multisig transactions. This function -- does not support segwit and P2SH-segwit inputs. When no transaction has a--- script for the input, the input is left as it is.+-- script for the input, the input is left as it is. Fails when one of the+-- transactions has no input at the index. The witness of the transaction+-- is kept. mergeTxInput :: Network -> Ctx ->@@ -482,6 +679,8 @@ ((ScriptOutput, Word64), Int) -> Either String Tx mergeTxInput net ctx txs tx ((so, val), i) = do+ when (i < 0 || any ((<= i) . length . (.inputs)) (tx : txs)) $+ Left "mergeTxInput: input index out of range" -- Ignore transactions with empty inputs let ins = map ((.script) . (!! i) . (.inputs)) txs sigRes <- mapM extractSigs $ filter (not . B.null) ins@@ -491,7 +690,7 @@ 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+ return $ Tx tx.version ins' tx.outputs tx.witness tx.locktime where go allSigs out rdmM = case out of@@ -524,21 +723,49 @@ {- Tx verification -} -- | 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.+-- given with a standard input that verifies ('verifyStdInput'), that no+-- two inputs spend the same coin, 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, are not checked, and neither+-- are lock times.+--+-- This is not a script interpreter. It answers 'True' only for the+-- standard kinds of input listed at 'verifyStdInput', and 'False' for any+-- other script, valid or not. For those kinds it follows consensus: it+-- does not answer 'True' for an input that no block can contain. verifyStdTx :: Network -> Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Bool verifyStdTx net ctx tx coins = not (null tx.inputs)- && all (verifyStdInput net ctx tx coins) [0 .. length tx.inputs - 1]- && spent <= available+ && Map.size (Map.fromList [(i.outpoint, ()) | i <- tx.inputs]) == length tx.inputs+ && all (verifyInput net ctx tx spent) [0 .. length tx.inputs - 1]+ && maybe False (\outs -> toSpend <= sum (map (toInteger . snd) outs)) (sequence spent.coins) where- spent = sum (map (toInteger . (.value)) tx.outputs)- available = sum [toInteger v | (_, v, _) <- matched]- matched = catMaybes (matchTemplate coins tx.inputs (\(_, _, o) txin -> o == txin.outpoint))+ -- The coins are looked up once for the whole transaction+ spent = spentCoins ctx tx coins+ toSpend = sum (map (toInteger . (.value)) tx.outputs) +-- | The coin of each input of a transaction, and the outputs that the+-- Taproot digest commits to. 'verifyStdTx' computes this once, so that the+-- time it takes does not grow with the square of the number of inputs.+data SpentCoins = SpentCoins+ { -- | the coin of each input, in order, when it is among those given+ coins :: [Maybe (ScriptOutput, Word64)],+ -- | the output every input spends, when every coin is given+ outputs :: Maybe [TxOut]+ }++spentCoins :: Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> SpentCoins+spentCoins ctx tx given =+ SpentCoins+ { coins = found,+ outputs = map (\(so, val) -> TxOut val (marshal ctx so)) <$> sequence found+ }+ where+ -- The first coin given for an outpoint is its coin+ byOutPoint = Map.fromListWith (\_ first -> first) [(o, (so, val)) | (so, val, o) <- given]+ found = map (\i -> Map.lookup i.outpoint byOutPoint) tx.inputs+ -- | 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@@ -547,16 +774,46 @@ -- 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.+--+-- The answer is never 'True' for an input that no block can contain:+--+-- * A multisig input has exactly as many signatures as its script+-- requires, each of a key of the script, in the order of the keys, and+-- no other item. @OP_CHECKMULTISIG@ takes that many signatures from the+-- top of the stack, and the item below them, its dummy element, must be+-- empty (BIP 147). So an item more, after the signatures or among them,+-- makes the operation fail.+-- * The input script of a SegWit program nested in pay-to-script-hash is+-- the push of the program and nothing else (BIP 141).+-- * A redeem script is at most 'maxRedeemScriptSize' bytes, and a witness+-- script at most 'maxWitnessScriptSize'.+-- * A network without SegWit has no witness inputs.+--+-- The answer can be 'False' for an input that a block may hold but that+-- nodes do not relay:+--+-- * A signature is read with 'decodeTxSig', which applies the relay rules:+-- low S and a defined hash type.+-- * A legacy multisig input with an empty item before its signatures+-- leaves that item on the stack. Consensus allows it outside a witness,+-- and the @CLEANSTACK@ rule of the relay policy does not.+--+-- An uncompressed key in a SegWit input is accepted, since a block may+-- hold it, although nodes do not relay it. verifyStdInput :: Network -> Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Int -> Bool-verifyStdInput net ctx tx coins i+verifyStdInput net ctx tx coins = verifyInput net ctx tx (spentCoins ctx tx coins)++-- | 'verifyStdInput' on coins that are looked up already+verifyInput :: Network -> Ctx -> Tx -> SpentCoins -> Int -> Bool+verifyInput net ctx tx spent i | i < 0 || i >= length tx.inputs = False- | otherwise = case coinOf (tx.inputs !! i) of+ | otherwise = case spent.coins !! i of Nothing -> False Just (so0, val) | isSegwit so0 ->- fromRight False $ (B.null inp &&) . verifySegwitInput so0 val <$> wp so0+ net.segWit && fromRight False ((B.null inp &&) . verifySegwitInput so0 val <$> wp so0) | isTaproot so0 ->- B.null inp && verifyTaprootInput so0+ net.segWit && B.null inp && verifyTaprootInput so0 | otherwise -> or $ rights@@ -565,7 +822,6 @@ ] where inp = (tx.inputs !! i).script- 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.@@ -580,11 +836,25 @@ wp :: ScriptOutput -> Either String (Maybe ScriptOutput, SimpleInput) wp so = decodeWitnessInput net ctx =<< viewWitnessProgram net ctx so ws + -- The witness program of an input script that is its push and nothing+ -- else, on a network that has SegWit nestedScriptOutput :: Either String ScriptOutput nestedScriptOutput = case unmarshal (net, ctx) inp of- Right (NestedWitnessInput so) -> Right so+ Right (NestedWitnessInput so)+ | net.segWit && inp == marshal (net, ctx) (NestedWitnessInput so) -> Right so _ -> Left "nestedScriptOutput: not a nested output" + -- OP_CHECKMULTISIG takes the signatures the script requires from the+ -- top of the stack, and each must verify against a later key than the+ -- one before. An empty item among them is no signature. An item more+ -- pushes a signature into the place of the dummy element, which must+ -- be empty. Only an empty item before the signatures does not fail the+ -- operation: it stays on the stack, which the relay rules refuse.+ multisigVerifies :: (SigHash -> Either String Hash256) -> [PubKey] -> Int -> [TxSignature] -> Bool+ multisigVerifies hashfun pubs r sigs =+ -- With exactly r items, r of them verify only when none is empty+ length sigs == r && countMulSig' ctx hashfun pubs sigs == r+ verifyLegacyInput :: ScriptOutput -> Word64 -> ScriptInput -> Bool verifyLegacyInput so val si = case (so, si) of (PayPK pub, RegularInput (SpendPK (TxSignature sig sh))) ->@@ -593,10 +863,10 @@ pubKeyAddr ctx pub == p2pkhAddr h && checkSig (theTxSigHash so val sh Nothing) sig pub.point (PayMulSig pubs r, RegularInput (SpendMulSig sigs)) ->- either (const False) (\out -> countMulSig net ctx tx out val i ((.point) <$> pubs) sigs == r) $+ either (const False) (\out -> multisigVerifies (txSigHash net tx out val i) ((.point) <$> pubs) r sigs) $ encodeOutput ctx so (PayScriptHash h, ScriptHashInput si' rdm) ->- payToScriptAddress ctx rdm == p2shAddr h && verifyLegacyInput rdm val (RegularInput si')+ payToScriptAddress ctx rdm == Right (p2shAddr h) && verifyLegacyInput rdm val (RegularInput si') _ -> False verifySegwitInput ::@@ -617,7 +887,7 @@ Just rdm'@(PayPK pub), SpendPK (TxSignature sig sh) ) ->- let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h+ let keytest = payToWitnessScriptAddress ctx rdm' == Right (p2wshAddr h) sighash = theTxSigHash so val sh $ Just rdm' pkpoint = pub.point verify = checkSig sighash sig pkpoint@@ -626,7 +896,7 @@ Just rdm'@(PayPKHash kh), SpendPKHash (TxSignature sig sh) pub ) ->- let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h+ let keytest = payToWitnessScriptAddress ctx rdm' == Right (p2wshAddr h) addrtest = addressHash (marshal ctx pub) == kh pkpoint = pub.point sighash = theTxSigHash so val sh $ Just rdm'@@ -636,33 +906,30 @@ Just rdm'@(PayMulSig pubs r), SpendMulSig sigs ) ->- let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h+ let keytest = payToWitnessScriptAddress ctx rdm' == Right (p2wshAddr h) pkpoints = (.point) <$> pubs hashfun sh = theTxSigHash so val sh $ Just rdm'- verify = countMulSig' ctx hashfun pkpoints sigs == r+ verify = multisigVerifies hashfun pkpoints r sigs in keytest && verify _ -> False verifyNestedInput :: 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+ PayScriptHash h -> payToScriptAddress ctx so' == Right (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+ -- The outputs every input spends, in order, which the Taproot digest+ -- commits to+ spentOutputs <- maybe (Left "a spent output is missing") Right spent.outputs let schnorr sigBytes annex leaf = do TaprootSignature sig sh <- decodeTaprootSig sigBytes- h <- taprootSigHash tx spent i sh annex leaf+ h <- taprootSigHash tx spentOutputs i sh annex leaf return (sig, h) case viewTaprootWitness ctx ws of Just (KeyPathSpend sigBytes annex) -> do@@ -684,7 +951,14 @@ PayWitness 1 program -> B.length program == 32 _ -> False --- | Count the number of valid signatures for a multi-signature transaction.+-- | Count the signatures of a multisig input that verify against the keys+-- of its script, taken in the order of the keys. An empty item counts for+-- nothing and passes over one key.+--+-- The count is not a verdict. An input is valid when it has exactly the+-- signatures its script requires and all of them count, which+-- 'verifyStdInput' checks. An input with an item more, or with an empty+-- item, can count enough signatures and still fail @OP_CHECKMULTISIG@. countMulSig :: Network -> Ctx ->
src/Haskoin/Transaction/Builder/Sign.hs view
@@ -63,6 +63,16 @@ -- 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.+--+-- A 'SigInput' is an instruction to the signer, and the signer follows it.+-- When it comes from someone else, for example as JSON, check it first:+--+-- * The hash type. Any type a node accepts is signed, including+-- 'sigHashNone' and 'sigHashSingle', which leave outputs unsigned.+-- * The value of a legacy coin. The legacy digest does not commit to it, so+-- a wrong value still gives a valid signature, and the fee is then not+-- what the value suggests. Only the previous transaction settles it.+-- The digests of SegWit, Taproot and Bitcoin Cash commit to the value. data SigInput = SigInput { -- | output script to spend script :: !ScriptOutput,@@ -111,6 +121,18 @@ -- | Sign a transaction by providing the 'SigInput' signing parameters and a -- list of private keys. The signature is computed deterministically as defined -- in RFC-6979.+--+-- An input is signed with the keys of the list that its coin asks for. An+-- input whose coin asks for none of them is left as it is, so that several+-- parties can sign in turn. The result is then 'Right' with that input+-- unsigned: use 'Haskoin.Transaction.Builder.verifyStdTx' to tell whether+-- the transaction is complete.+--+-- A SegWit version 0 coin is signed with a compressed key only, and fails+-- when its key is uncompressed. Nodes do not relay such a spend+-- (@SCRIPT_VERIFY_WITNESS_PUBKEYTYPE@ in Bitcoin Core), although a block+-- may hold it. 'signInput' signs with the key it is given, in the form it+-- is given. signTx :: Network -> Ctx ->@@ -127,10 +149,14 @@ | otherwise = foldM go otx $ findInputIndex ((.outpoint) . fst) sigis ti where ti = otx.inputs+ -- The public keys of the secret keys are derived once for the whole+ -- transaction, not once per input+ index = keyIndex ctx allKeys go tx (sigi@(SigInput so _ _ sh rdmM, _), i) = do- keys <- sigKeys ctx so rdmM allKeys+ keys <- indexedSigKeys index so rdmM if isTaprootOutput so then do+ unless net.segWit $ Left "signTx: this network has no Taproot" 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@@ -142,7 +168,7 @@ 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+ | 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@@ -158,8 +184,11 @@ -- '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.+-- range, when the spent outputs do not match the inputs, when the hash+-- type is not one 'isTaprootSigHash' accepts, when the output that the+-- input spends is not a Taproot output, or when the secret key is not the+-- key of that output: a signature of another key would make a transaction+-- that no node accepts. signTaprootInput :: Ctx -> Tx ->@@ -172,9 +201,15 @@ SecKey -> Either String Tx signTaprootInput ctx tx spent i sh secret = do+ -- The digest checks the index and the number of spent outputs h <- taprootSigHash tx spent i sh Nothing Nothing- let signature = TaprootSignature (signSchnorr ctx secret (hashToMsg h)) sh- stacks+ outputKey <- case B.unpack (spent !! i).script of+ 0x51 : 0x20 : program | length program == 32 -> Right (B.pack program)+ _ -> Left "signTaprootInput: the output spent is not a Taproot output"+ unless (exportXOnlyPubKey ctx (derivePubKey ctx secret) == outputKey) $+ Left "signTaprootInput: the key is not the key of the output"+ signature <- taprootSignature (signSchnorr ctx secret (hashToMsg h)) sh+ let stacks | null tx.witness = replicate (length tx.inputs) [] | otherwise = tx.witness unless (length stacks == length tx.inputs) $@@ -183,6 +218,13 @@ -- | Sign a single input in a transaction deterministically (RFC-6979). The -- nesting flag only affects the behavior of segwit inputs.+--+-- The key must be one the coin asks a signature of: the key of a+-- pay-to-public-key coin, the key whose hash a coin pays to, or a key of a+-- multisig. Any other key fails, since its signature would make an input+-- that no node accepts. The key is used in the form given, compressed or+-- not. That is the way to spend a SegWit coin of an uncompressed key on+-- purpose, which 'signTx' refuses. signInput :: Network -> Ctx ->@@ -195,8 +237,11 @@ signInput net ctx tx i (sigIn@(SigInput so val _ _ rdmM), nest) key = do when (isTaprootOutput so) $ Left "signInput: a Taproot input is signed with signTaprootInput"+ let pub = derivePublicKey ctx key+ unless (keyOfCoin ctx so rdmM pub) $+ Left "signInput: the key is not a key of the coin" sig <- makeSignature net ctx tx i sigIn key- si <- buildInput net ctx tx i so val rdmM sig $ derivePublicKey ctx key+ si <- buildInput net ctx tx i so val rdmM sig pub w <- updatedWitnessData net ctx tx i so si return tx {inputs = nextTxIn so si, witness = w} where@@ -207,6 +252,19 @@ | isSegwit so' = tx.inputs | otherwise = updateIndex i tx.inputs (f si) +-- | Whether the public key is one that the coin asks a signature of+keyOfCoin :: Ctx -> ScriptOutput -> Maybe RedeemScript -> PublicKey -> Bool+keyOfCoin ctx so rdmM pub = case (so, rdmM) of+ (PayPK p, Nothing) -> p == pub+ (PayPKHash h, Nothing) -> keyHash == h+ (PayMulSig ps _, Nothing) -> pub `elem` ps+ (PayScriptHash _, Just rdm) -> keyOfCoin ctx rdm Nothing pub+ (PayWitnessPKHash h, _) -> keyHash == h+ (PayWitnessScriptHash _, Just rdm) -> keyOfCoin ctx rdm Nothing pub+ _ -> False+ where+ keyHash = addressHash (marshal ctx pub)+ -- | Add the witness data of the transaction given segwit parameters for an input. -- -- @since 0.11.0.0@@ -251,45 +309,74 @@ -- 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.+--+-- A legacy coin is matched with each key in its compressed and in its+-- uncompressed form. A SegWit version 0 coin, native or nested, takes a+-- compressed key only: when the key it asks for is uncompressed, the+-- result is an error, because nodes do not relay such a spend+-- (@SCRIPT_VERIFY_WITNESS_PUBKEYTYPE@ in Bitcoin Core). sigKeys :: Ctx -> ScriptOutput -> Maybe RedeemScript -> [SecKey] -> Either String [PrivateKey]-sigKeys ctx so rdmM keys =- case (so, rdmM) of- (PayPK p, Nothing) ->- return . map fst . maybeToList $ find ((== p) . snd) zipKeys- (PayPKHash h, Nothing) -> return $ keyByHash h- (PayMulSig ps r, Nothing) ->- return $ map fst $ take r $ filter ((`elem` ps) . snd) zipKeys- (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"+sigKeys ctx so rdmM keys = indexedSigKeys (keyIndex ctx keys) so rdmM++-- | The secret keys of a signer with what is derived from them, so that it+-- is derived once for a whole transaction+data KeyIndex = KeyIndex+ { -- | each key in its compressed and then in its uncompressed form, with+ -- its public key and the hash of that public key+ forms :: [(PrivateKey, PublicKey, Hash160)],+ -- | for each key, the Taproot output keys it spends, as x-only bytes,+ -- with the secret key of each: the key itself, then its tweak without+ -- a script tree+ outputKeys :: [[(B.ByteString, SecKey)]]+ }++keyIndex :: Ctx -> [SecKey] -> KeyIndex+keyIndex ctx keys =+ KeyIndex+ { forms =+ [ (PrivateKey k compressed, pub, addressHash (marshal ctx pub))+ | k <- keys,+ let point = derivePubKey ctx k,+ compressed <- [True, False],+ let pub = PublicKey point compressed+ ],+ outputKeys =+ [ (xOnly k, k) : [(xOnly tweaked, tweaked) | Just tweaked <- [taprootOutputSecKey ctx k Nothing]]+ | k <- keys+ ]+ } where- zipKeys =- [ (prv, pub)- | k <- keys,- t <- [True, False],- let prv = wrapSecKey t k,- let pub = derivePublicKey ctx prv- ]- 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+ xOnly = exportXOnlyPubKey ctx . derivePubKey ctx +-- | 'sigKeys' on keys that are indexed already+indexedSigKeys :: KeyIndex -> ScriptOutput -> Maybe RedeemScript -> Either String [PrivateKey]+indexedSigKeys index = go False+ where+ -- The flag tells whether the keys sign inside a SegWit version 0 witness+ go witness so rdmM = case (so, rdmM) of+ (PayPK p, Nothing) ->+ compressedIn witness . take 1 $ [prv | (prv, pub, _) <- index.forms, pub == p]+ (PayPKHash h, Nothing) ->+ compressedIn witness . take 1 $ [prv | (prv, _, keyHash) <- index.forms, keyHash == h]+ (PayMulSig ps r, Nothing) ->+ compressedIn witness . take r $ [prv | (prv, pub, _) <- index.forms, pub `elem` ps]+ (PayScriptHash _, Just rdm) -> go witness rdm Nothing+ (PayWitnessPKHash h, _) -> go True (PayPKHash h) Nothing+ (PayWitnessScriptHash _, Just rdm) -> go True rdm Nothing+ (PayWitness 1 program, Nothing)+ | B.length program == 32 ->+ return [PrivateKey k True | Just k <- map (lookup program) index.outputKeys]+ _ -> Left "sigKeys: Could not decode output script"+ compressedIn witness found+ | witness && not (all (.compress) found) =+ Left "sigKeys: a SegWit input is signed with a compressed key only, and this coin pays to an uncompressed key"+ | otherwise = Right found+ -- | Construct an input for a transaction given a signature, public key and data -- about the previous output. buildInput ::@@ -361,6 +448,20 @@ -- 'SigInput', since every signature there must carry it. Fails when the -- input index is out of range or the output script cannot be encoded. --+-- The signer refuses what a node would refuse, with the reason:+--+-- * A hash type that does not fit in the one byte a signature carries, one+-- that is not 'sigHashAll', 'sigHashNone' or 'sigHashSingle' with or+-- without 'setAnyoneCanPay' (the test of @IsDefinedHashtypeSignature@ in+-- Bitcoin Core), and the fork id flag on a network without a fork id.+-- On Bitcoin Cash this refuses @SIGHASH_UTXOS@ too, whose digest the+-- library does not compute.+-- * A pay-to-script-hash coin without its redeem script, with a redeem+-- script over 'maxRedeemScriptSize' bytes, or with one that does not+-- hash to the coin. The same for a pay-to-witness-script-hash coin and+-- its witness script, with 'maxWitnessScriptSize'.+-- * A SegWit coin on a network without SegWit.+-- -- 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@@ -369,18 +470,43 @@ -- 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.+--+-- The key is not checked against the coin here: 'signInput' does that. makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String TxSignature makeSignature net ctx tx i (SigInput so val _ sh rdmM) key = do+ when (isSegwit so && not net.segWit) $+ Left "makeSignature: this network has no SegWit"+ when (sh > 0xff) $+ Left $ "makeSignature: the hash type " <> shown <> " does not fit in the one byte of a signature"+ when (isNothing net.sigHashForkId && hasForkIdFlag sh) $+ Left $ "makeSignature: the hash type " <> shown <> " has the fork id flag, which this network does not use"+ when (isSigHashUnknown sh') $+ Left $ "makeSignature: the hash type " <> shown <> " is not one a node accepts"+ committedScript 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+ shown = show (toInteger sh) sh' | isJust net.sigHashForkId = setForkIdFlag sh | otherwise = sh signsConstantDigest = isSigHashSingle sh' && not (isSegwit so) && isNothing net.sigHashForkId+ -- A coin that commits to a script is signed only with that script+ committedScript = case (so, rdmM) of+ (PayScriptHash h, Just rdm) -> do+ committed <- p2shScriptHash =<< encodeOutput ctx rdm+ unless (committed == h) $ Left "makeSignature: the redeem script does not hash to the coin"+ (PayScriptHash _, Nothing) ->+ Left "makeSignature: a pay-to-script-hash coin needs its redeem script"+ (PayWitnessScriptHash h, Just rdm) -> do+ committed <- p2wshScriptHash =<< encodeOutput ctx rdm+ unless (committed == h) $ Left "makeSignature: the witness script does not hash to the coin"+ (PayWitnessScriptHash _, Nothing) ->+ Left "makeSignature: a pay-to-witness-script-hash coin needs its witness script"+ _ -> Right () -- | A function which selects the digest algorithm and parameters as appropriate --
src/Haskoin/Transaction/Common.hs view
@@ -36,6 +36,7 @@ txHashToHex, nosigTxHash, hasWitness,+ decodeTx, parseLegacyTx, nullOutPoint, )@@ -180,26 +181,40 @@ -- | 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.+--+-- Bitcoin Core charges a transaction that is dense in signature operations+-- for 5 virtual bytes per operation when that is more than this size+-- (@GetVirtualTransactionSize@ in @policy/policy.cpp@). This function does+-- not count signature operations. 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.+-- none. Only the stacks of the inputs count: the witness list has one+-- stack per input, and a stack beyond the inputs belongs to no input and+-- is not written. hasWitness :: Tx -> Bool-hasWitness = any (not . null) . (.witness)+hasWitness tx = any (not . null) (take (length tx.inputs) tx.witness) +-- | Read a transaction from bytes that hold the transaction and nothing+-- else, as @DecodeHexTx@ requires in Bitcoin Core. 'deserialize' reads a+-- transaction from the front of a longer input, as a block needs it to,+-- and so it does not notice bytes that follow. Use this function for a+-- transaction that arrives on its own.+decodeTx :: ByteString -> Either String Tx+decodeTx = runGetS $ do+ tx <- deserialize+ done <- isEmpty+ unless done $ fail "Trailing bytes after transaction"+ return tx+ -- | 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 whole <=< decodeHex) . cs+ fromMaybe e . (eitherToMaybe . decodeTx <=< 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@@ -302,14 +317,18 @@ putWitnessData :: (MonadPut m) => WitnessData -> m () putWitnessData = mapM_ (putVarIntList putVarIntBytes) +-- | The witness of the JSON form has one stack per input, or none at all:+-- a stack without an input would not be part of the transaction. instance FromJSON Tx where- parseJSON = withObject "Tx" $ \o ->- Tx- <$> o .: "version"- <*> o .: "inputs"- <*> o .: "outputs"- <*> (mapM (mapM f) =<< o .: "witnessdata")- <*> o .: "locktime"+ parseJSON = withObject "Tx" $ \o -> do+ version <- o .: "version"+ inputs <- o .: "inputs"+ outputs <- o .: "outputs"+ witness <- mapM (mapM f) =<< o .: "witnessdata"+ locktime <- o .: "locktime"+ unless (null witness || length witness == length inputs) $+ fail "The witness data must have one stack per input, or none"+ return Tx {..} where f = maybe mzero return . decodeHex
src/Haskoin/Transaction/Psbt.hs view
@@ -43,16 +43,28 @@ -- 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.+-- missing, bytes after the last map. So is what Bitcoin Core refuses when+-- it reads a PSBT: a key type that is not a compact size integer in its+-- shortest form, a public key that is not a point of the curve, a partial+-- signature that is not in the encoding of BIP 66 with a defined sighash+-- byte, a previous transaction that is not the one its input spends, and+-- a script tree that is not whole. --+-- The amounts a PSBT states are claims of whoever wrote it, unless the+-- input carries its previous transaction. 'inputCoins' tells which coins+-- are checked, 'psbtFee' gives the fee from checked coins only, and the+-- signers want the previous transaction of a SegWit version 0 coin+-- ('requirePreviousTx'). The derivation records of outputs are claims+-- too: to treat an output as change, derive its key and compare.+-- -- 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'.+-- extractor ('psbtTransaction'), which wants every input final. An updater+-- is the functions that insert fields, and 'setInputSequence'. module Haskoin.Transaction.Psbt ( -- * A PSBT Psbt (..),@@ -65,6 +77,7 @@ psbtId, combinePsbt, psbtTransaction,+ psbtPartialTransaction, -- * The inputs and outputs of version 2 addInput,@@ -153,6 +166,11 @@ lookupProprietary, insertProprietary, + -- * The coins of a PSBT+ InputCoin (..),+ inputCoins,+ psbtFee,+ -- * Signing Signer (..), defaultSigHashes,@@ -181,7 +199,7 @@ 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.Maybe (catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMaybe, maybeToList) import Data.Serialize qualified as S import Data.Word (Word32, Word64, Word8) import GHC.Generics (Generic)@@ -195,17 +213,22 @@ import Haskoin.Script.SigHash import Haskoin.Script.Standard import Haskoin.Transaction.Builder.Sign (SigInput (..), makeSigHash, makeSignature)-import Haskoin.Transaction.Common+import Haskoin.Transaction.Common hiding (decodeTx) import Haskoin.Transaction.Segwit (isSegwit) import Haskoin.Transaction.Taproot import Haskoin.Util import Numeric (showHex)+import Text.Read (readMaybe) {- 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.+-- of. The type of a key is a compact size integer. Every type the spec+-- defines fits one byte. A type from 253 on takes three or five bytes on+-- the wire: its first byte, 0xfd or 0xfe, is the 'keyType' here, and the+-- other bytes of the type are the start of the 'keyData', where+-- 'decodePsbt' requires them to be the shortest form of the type. data Key = Key { keyType :: !Word8, keyData :: !ByteString@@ -235,6 +258,13 @@ -- 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.+--+-- In version 2 the record so holds the transaction twice. 'signPsbt' signs+-- 'unsignedTx', and 'encodePsbt' writes the maps. A record update that+-- changes one and not the other gives a PSBT whose bytes describe another+-- transaction than the one that is signed. Change the transaction of such a+-- PSBT with 'addInput', 'addOutput' and 'psbtOfMaps', which keep the two in+-- step. data Psbt = Psbt { version :: !PsbtVersion, unsignedTx :: !Tx,@@ -275,8 +305,28 @@ tx <- case version of Version0 -> unsignedTxOf globals Version2 -> transactionOf globals inputs outputs+ forM_ (zip3 [0 :: Int ..] tx.inputs inputs) $ \(i, input, m) ->+ first (("Input " <> show i <> ": ") <>) (checkPreviousTx input m) return $ Psbt version tx (Map.delete unsignedTxKey globals) inputs outputs +-- | The previous transaction of an input, when its map has one, must be+-- the transaction the input spends from and have the output it spends, as+-- Bitcoin Core requires when it reads a PSBT ("Non-witness UTXO does not+-- match outpoint hash", psbt.h)+checkPreviousTx :: TxIn -> PsbtMap -> Either String ()+checkPreviousTx input m = do+ parentM <- nonWitnessUtxo m+ forM_ parentM (previousOutput input)++-- | The output an input spends, from its previous transaction: the+-- transaction must be the one the input names, and have that output+previousOutput :: TxIn -> Tx -> Either String TxOut+previousOutput input parent = do+ unless (txHash parent == input.outpoint.hash) $+ Left "the previous transaction is not the one the input spends"+ maybe (Left "the input spends an output its previous transaction does not have") Right $+ listToMaybe (drop (fromIntegral input.outpoint.index) parent.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@@ -341,11 +391,28 @@ 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.+-- | The transaction of a PSBT whose inputs are all final, with their final+-- scripts and witnesses, as the transaction extractor of the spec builds+-- it. BIP 174 has the extractor check "whether all inputs have complete+-- scriptSigs and scriptWitnesses" first, and Bitcoin Core gives no+-- transaction otherwise (@FinalizeAndExtractPSBT@), so an input that is+-- not final is refused here. 'psbtPartialTransaction' takes such a PSBT.+--+-- The final scripts are taken as they are: nothing runs them. Finalize+-- with 'finalizePsbt', which verifies the signatures it uses, or verify+-- the transaction before it is sent. psbtTransaction :: Psbt -> Either String Tx psbtTransaction psbt = do+ forM_ (zip [0 :: Int ..] psbt.inputs) $ \(i, m) ->+ unless (isFinal m) $ Left ("Input " <> show i <> " is not final")+ psbtPartialTransaction psbt++-- | The transaction of a PSBT with the final script and witness of every+-- input that has them. An input without them stays unsigned, so the+-- transaction may not be valid yet: this is for looking at a PSBT that is+-- still being signed, not for sending. 'psbtTransaction' is the extractor.+psbtPartialTransaction :: Psbt -> Either String Tx+psbtPartialTransaction psbt = do finals <- forM (zip psbt.unsignedTx.inputs psbt.inputs) $ \(input, m) -> do stack <- fromMaybe [] <$> finalScriptWitness m let scriptSig = fromMaybe B.empty (finalScriptSig m)@@ -363,15 +430,23 @@ -- 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.+-- its output. The map is checked against the spec as a map read from+-- bytes is: a field out of shape, or a previous transaction that is not+-- the one the input spends, is refused. 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]+ let added = withInputFields input m+ inputs = psbt.inputs <> [added] globals = withCounts (length inputs) (length psbt.outputs) psbt.globals+ place = "input " <> show (length psbt.inputs)+ -- The same checks as when a PSBT is read, so that the bytes written are+ -- bytes 'decodePsbt' reads+ checkMap place Version2 inputFields added+ first ((capital place <> ": ") <>) (checkPreviousTx input added) 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"@@ -385,8 +460,10 @@ 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]+ let added = withOutputFields output m+ outputs = psbt.outputs <> [added] globals = withCounts (length psbt.inputs) (length outputs) psbt.globals+ checkMap ("output " <> show (length psbt.outputs)) Version2 outputFields added tx <- transactionOf globals psbt.inputs outputs return $ Psbt Version2 tx globals psbt.inputs outputs @@ -652,12 +729,41 @@ then return m else do keyBytes <- bytesOf keyLength what+ either refuse return (checkKeyType what keyBytes) 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) +-- | The type of a key is a compact size integer at the start of the key.+-- Every type the spec defines is below 0xfd and so is the first byte. A+-- type from 0xfd on takes three, five or nine bytes: it must be whole, in+-- its shortest form, and at most 'maxCompactSize', as Bitcoin Core reads+-- it (@ReadCompactSize@ in serialize.h). Such a key is kept as its first+-- byte and, as its data, the bytes after it.+checkKeyType :: String -> ByteString -> Either String ()+checkKeyType what keyBytes = case B.unpack (B.take 1 keyBytes) of+ [0xfd] -> longer 2 0xfd+ [0xfe] -> longer 4 0x10000+ [0xff] -> longer 8 0x100000000+ _ -> Right ()+ where+ longer :: Int -> Word64 -> Either String ()+ longer n least = do+ let bytes = B.take n (B.drop 1 keyBytes)+ unless (B.length bytes == n) $+ Left (capital what <> " has a key that ends inside its type")+ let value = B.foldr (\b acc -> acc `shiftL` 8 .|. fromIntegral b) 0 bytes+ when (value < least) $+ Left (capital what <> " has a key whose type is not in its shortest form")+ when (value > maxCompactSize) $+ Left (capital what <> " has a key whose type is beyond any type")++-- | The largest compact size integer Bitcoin Core reads: its @MAX_SIZE@+maxCompactSize :: Word64+maxCompactSize = 0x02000000+ psbtParser :: BytesParser Psbt psbtParser = do magic <- bytesOf 5 "the magic bytes"@@ -780,13 +886,25 @@ capital (c : rest) = toUpper c : rest capital [] = [] --- | A key as a refusal names it: its type in hexadecimal, and its data+-- | A key as a refusal names it: its type in hexadecimal, and its data.+-- Only the first 'maxShownKeyData' bytes of the data are written, then+-- its length, so that whoever writes a PSBT cannot make a refusal as+-- long as they like. 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))+ "0x" <> hexByte key.keyType <> (if B.null key.keyData then "" else " with the data " <> shown) where hexByte b = (if b < 16 then "0" else "") <> showHex b ""+ hexOf = concatMap hexByte . B.unpack+ shown+ | B.length key.keyData <= maxShownKeyData = hexOf key.keyData+ | otherwise = hexOf (B.take maxShownKeyData key.keyData) <> "… (" <> show (B.length key.keyData) <> " bytes)" +-- | How many bytes of the data of a key a refusal writes: enough for a+-- compressed public key, which is 33 bytes+maxShownKeyData :: Int+maxShownKeyData = 40+ {- The shapes of the spec -} -- | The data of a key of the spec: none, a public key, an extended public@@ -835,7 +953,7 @@ 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")+ XpubData -> unless (isXpubBytes key.keyData) $ 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")@@ -846,16 +964,61 @@ 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.+-- | Whether the bytes are a public key, compressed or not: they have the+-- length and the first byte of one, and are a point of the curve, as+-- Bitcoin Core requires of the keys of a PSBT (@IsFullyValid@ in psbt.h).+-- The point is checked through the 'Read' instance of the binding, which+-- parses the hexadecimal form of a key without a context. 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+isPublicKeyBytes bytes = shaped && isJust (readMaybe (show (encodeHex bytes)) :: Maybe PubKey)+ where+ shaped = case B.unpack (B.take 1 bytes) of+ [0x02] -> B.length bytes == 33+ [0x03] -> B.length bytes == 33+ [0x04] -> B.length bytes == 65+ _ -> False +-- | Whether the bytes are an extended public key as BIP 32 serializes+-- one: 78 bytes that end with a compressed public key+isXpubBytes :: ByteString -> Bool+isXpubBytes bytes = B.length bytes == 78 && B.length key == 33 && isPublicKeyBytes key+ where+ key = B.drop 45 bytes++-- | Whether the bytes are a signature as a script holds one: in the+-- encoding BIP 66 requires, with its sighash byte last. This is+-- @IsValidSignatureEncoding@ of Bitcoin Core (script/interpreter.cpp),+-- line for line. It checks the shape of the bytes, not the range of the+-- two numbers.+isSignatureEncoding :: ByteString -> Bool+isSignatureEncoding sig =+ -- Minimum and maximum size+ size >= 9+ && size <= 73+ -- A signature is of type 0x30 (compound)+ && at 0 == 0x30+ -- The length covers the entire signature, without the sighash byte+ && fromIntegral (at 1) == size - 3+ -- The length of the S element is still inside the signature+ && 5 + lenR < size+ -- The lengths of the two elements add up to the signature+ && lenR + lenS + 7 == size+ -- R is an integer, not empty, not negative, with no needless zero byte+ && at 2 == 0x02+ && lenR /= 0+ && at 4 .&. 0x80 == 0+ && not (lenR > 1 && at 4 == 0x00 && at 5 .&. 0x80 == 0)+ -- S is an integer, not empty, not negative, with no needless zero byte+ && at (lenR + 4) == 0x02+ && lenS /= 0+ && at (lenR + 6) .&. 0x80 == 0+ && not (lenS > 1 && at (lenR + 6) == 0x00 && at (lenR + 7) .&. 0x80 == 0)+ where+ size = B.length sig+ at = B.index sig+ lenR = fromIntegral (at 3) :: Int+ lenS = fromIntegral (at (5 + lenR)) :: Int+ -- The unsigned transaction is checked apart, by 'unsignedTxOf', and the -- version by 'versionOf' globalFields :: Fields@@ -885,7 +1048,7 @@ Map.fromList [ (nonWitnessUtxoType, plain txValue), (witnessUtxoType, plain txOutValue),- (partialSigType, Field PublicKeyData [Version0, Version2] (const anyValue)),+ (partialSigType, Field PublicKeyData [Version0, Version2] (const partialSigValue)), (sigHashTypeType, plain word32Value), (inRedeemScriptType, plain scriptValue), (inWitnessScriptType, plain scriptValue),@@ -906,6 +1069,14 @@ ] where txValue value = () <$ (decodeTx value :: Either String Tx)+ -- Bitcoin Core checks a partial signature with the flags DERSIG and+ -- STRICTENC when it reads a PSBT: the encoding of BIP 66, and a sighash+ -- byte that is ALL, NONE or SINGLE, with ANYONECANPAY or not. The fork+ -- id flag is let through here, since the decoder knows no network; the+ -- signer and the finalizer check it.+ partialSigValue value =+ unless (isSignatureEncoding value && not (isSigHashUnknown (fromIntegral (B.last value)))) $+ Left "is not a signature in the encoding of BIP 66 with a defined sighash byte" txOutValue value = () <$ (decodeTxOut value :: Either String TxOut) witnessValue value = () <$ decodeWitness value txHashValue value = () <$ decodeTxHash value@@ -946,8 +1117,17 @@ hashValue :: ByteString -> Either String () hashValue value = unless (B.length value == 32) $ Left "is not a 32-byte hash" +-- | A script tree as Bitcoin Core accepts one when it reads a PSBT: not+-- empty, no leaf deeper than 128, every leaf version even, and the leaves+-- those of one whole tree in depth-first order tapTreeValue :: ByteString -> Either String ()-tapTreeValue value = () <$ decodeTapTree value+tapTreeValue value = do+ leaves <- decodeTapTree value+ when (null leaves) $ Left "is an empty script tree"+ when (any ((> maxControlBlockNodes) . fromIntegral . (.depth)) leaves) $+ Left ("has a leaf deeper than " <> show maxControlBlockNodes)+ when (any (odd . (.version)) leaves) $ Left "has a leaf with an odd leaf version"+ when (isNothing (tapTreeFromLeaves leaves)) $ Left "is not a whole script tree" word32Value :: ByteString -> Either String () word32Value value = () <$ decodeWord32le value@@ -1056,7 +1236,9 @@ insertInputDerivation :: Ctx -> PublicKey -> Derivation -> PsbtMap -> PsbtMap insertInputDerivation = insertDerivation inDerivationType --- | The derivations of the keys of an output: PSBT_OUT_BIP32_DERIVATION+-- | The derivations of the keys of an output: PSBT_OUT_BIP32_DERIVATION.+-- A record is a claim of whoever wrote the PSBT. To treat an output as+-- change, derive the key and compare it with the script of the output. outputDerivations :: Ctx -> PsbtMap -> Either String [(PublicKey, Derivation)] outputDerivations = derivationsOf outDerivationType @@ -1276,7 +1458,8 @@ insertInputTapDerivation = insertTapDerivation inTapDerivationType -- | The derivations of the x-only keys of a Taproot output:--- PSBT_OUT_TAP_BIP32_DERIVATION+-- PSBT_OUT_TAP_BIP32_DERIVATION. A record is a claim of whoever wrote the+-- PSBT, as for 'outputDerivations'. outputTapDerivations :: Ctx -> PsbtMap -> Either String [(PubKey, TapDerivation)] outputTapDerivations = tapDerivationsOf outTapDerivationType @@ -1449,7 +1632,8 @@ -- | 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.+-- sighash types it agrees to sign with, and whether it wants the previous+-- transaction of a SegWit version 0 coin. data Signer = Signer { -- | the private key of the public key a derivation record names keyOfRecord :: PublicKey -> Derivation -> Maybe SecKey,@@ -1458,7 +1642,19 @@ -- | 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]+ acceptedSigHashes :: [SigHash],+ -- | whether a SegWit version 0 coin (P2WPKH or P2WSH, nested in P2SH+ -- or not) is signed only when the input carries its previous+ -- transaction. A BIP 143 signature commits to the amount of its own+ -- coin and to no other. With the witness output alone, whoever writes+ -- the PSBT can lie about the amount of one coin of the signer in each+ -- of two signing rounds, keep the valid signature of each round, and+ -- make a transaction that pays the difference as fee+ -- (CVE-2020-14199). BIP 174 lets a signer "fail to sign a segwit+ -- input if a non-witness UTXO is not provided" for this reason. A+ -- Taproot signature commits to the amount of every coin and needs no+ -- such care.+ requirePreviousTx :: Bool } -- | The sighash types a signer accepts unless told otherwise:@@ -1480,7 +1676,11 @@ 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+-- key and for the hash of its public key in either form. It wants the+-- previous transaction of a SegWit version 0 coin ('requirePreviousTx').+-- A caller that has checked the amounts of its coins another way, for+-- example against its own records, turns that off with+-- @(keySigner ctx key) {requirePreviousTx = False}@. keySigner :: Ctx -> SecKey -> Signer keySigner ctx secret = Signer@@ -1488,25 +1688,30 @@ 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+ acceptedSigHashes = defaultSigHashes,+ requirePreviousTx = True } -- | 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.+-- path of the extended key. A path the key cannot follow, such as one+-- deeper than the 255 levels of BIP 32 or one with a hardened step+-- below a key that cannot derive it, names no key. It has no key for a+-- hash, since it cannot know which path to derive. Like 'keySigner', it+-- wants the previous transaction of a SegWit version 0 coin. 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+ secret <- (.key) <$> eitherToMaybe (tryDerivePath ctx path xprv) guard (public.point == derivePubKey ctx secret) return secret, keyOfHash = const Nothing,- acceptedSigHashes = defaultSigHashes+ acceptedSigHashes = defaultSigHashes,+ requirePreviousTx = True } where own = xPubFP ctx (deriveXPubKey ctx xprv)@@ -1521,30 +1726,49 @@ -- 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.+-- 174 requires. --+-- The keys are those the script that is signed names: the script of the+-- coin, its redeem script or its witness script. The signer is asked for+-- the key of a derivation record only when that script names the key of+-- the record, and for the key of each hash the script names. A key that+-- the script does not name gets no signature, whatever the records say,+-- and a P2SH coin without its redeem script or a P2WSH coin without its+-- witness script gets none either, as BIP 174 has it: "If a Signer cannot+-- sign a transaction, it must not add a Partial Signature."+--+-- The sighash is the one the input asks for. When it asks for none it is+-- SIGHASH_ALL, with the fork id flag on a network that has one. A type no+-- node accepts, or one the signer does not accept ('acceptedSigHashes'),+-- is refused on any input that has its coin, as Bitcoin Core refuses a+-- PSBT with a sighash type the caller did not ask for.+--+-- An input the signer holds a key for is refused, with the reason, when:+--+-- * its redeem script does not hash to the coin, or its witness script to+-- the witness program;+-- * it is a legacy coin known from its witness output only;+-- * it is a SegWit version 0 coin known from its witness output only and+-- the signer wants the previous transaction ('requirePreviousTx');+-- * the key is uncompressed and the coin is a SegWit version 0 one, which+-- no node relays and Bitcoin Core does not sign;+-- * it is a legacy coin asked to sign SIGHASH_SINGLE with no output at its+-- index, as 'makeSignature' explains.+-- -- 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.+-- its leaf scripts that is a single key check (@<key> OP_CHECKSIG@) and+-- that its control block commits to the output key, 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.+-- An input without its coin, with a final script already, with a coin,+-- redeem script or witness script that is no standard template, or for+-- which the signer holds no key, 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+ let 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)@@ -1552,34 +1776,61 @@ Right coinScript <- unmarshal ctx coin.script -> do sighashM <- sigHashType m if isTaprootCoin coinScript- then signTaproot i m coin coinScript sighashM allCoins+ then signTaproot i m 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+ -- A signature per key the signer holds among those the script names,+ -- for a coin signed with ECDSA signEcdsa i input m coin coinScript witnessOnly sighashM = do- let sighash = fromMaybe sigHashAll sighashM+ let sighash = fromMaybe (defaultEcdsaSigHash net) 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)])+ scriptsM <- inputScripts ctx coinScript m+ case scriptsM of+ Nothing -> return (m, [])+ Just scripts -> do+ keys <- heldKeys m (maybe [] (scriptKeyHashes ctx) scripts.signedScript)+ if null keys+ then return (m, [])+ else do+ forM_ scripts.mismatch Left+ let witnessCoin = isSegwit scripts.program+ when (witnessOnly && not witnessCoin) $+ Left "the coin is given as a witness output only, which may not be signed with a non-witness signature"+ when (witnessOnly && witnessCoin && signer.requirePreviousTx) $+ Left "the coin is given as a witness output only, and the signer requires the previous transaction of a SegWit version 0 coin"+ when (witnessCoin && not (all (.compress) keys)) $+ Left "a SegWit version 0 coin may not be signed with an uncompressed key"+ let sigInput = signingInput input coin sighash scripts+ 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]) + -- The keys the signer holds among those whose hashes the script that+ -- is signed names. The signer is asked for the key of a derivation+ -- record only when the hash of the key of the record is one of them,+ -- so the work it does is bounded by the script, however many records+ -- the PSBT carries. A key the signer gives is kept only when it is the+ -- key that was asked for.+ heldKeys :: PsbtMap -> [Hash160] -> Either String [PrivateKey]+ heldKeys m named = do+ let namedRecords = [(key, value) | (key, value) <- Map.toAscList m, key.keyType == inDerivationType, addressHash key.keyData `elem` named]+ recordKeys <- forM namedRecords $ \(key, value) -> do+ public <- first (const ("The key " <> showKey key <> " is not a public key")) (unmarshal ctx key.keyData)+ derivation <- first (("The value of the key " <> showKey key <> " ") <>) (decodeDerivation value)+ return [PrivateKey secret public.compress | Just secret <- [signer.keyOfRecord public derivation], derivePubKey ctx secret == public.point]+ let hashKeys = [key | h <- named, Just key <- [signer.keyOfHash h], addressHash (marshal ctx (derivePublicKey ctx key)) == h]+ return $ nubBy (\a b -> derivePublicKey ctx a == derivePublicKey ctx b) (concat recordKeys <> hashKeys)+ -- 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+ signTaproot i m 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")@@ -1591,9 +1842,12 @@ 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]+ -- The secret key of an x-only key, by a record of that key which+ -- the test accepts, or by the hash of the key. A key the signer+ -- gives is kept only when it is the key that was asked for.+ let heldXOnly accepts public =+ listToMaybe . filter (sameX public . derivePubKey ctx) . catMaybes $+ [signer.keyOfRecord (PublicKey candidate True) d.derivation | (recordKey, d) <- records, sameX recordKey public, accepts d, candidate <- bothParities public] <> [(.key) <$> signer.keyOfHash (addressHash (marshal ctx (PublicKey candidate True))) | candidate <- bothParities public] keySig <- case internalM of Nothing -> return []@@ -1601,18 +1855,28 @@ 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+ -- Any record of the internal key will do: BIP 371 lists in a+ -- record the leaves the key is in, and the internal key may be+ -- in a leaf too+ case heldXOnly (const True) 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)]+ signature <- taprootSignature (signSchnorr ctx tweaked (hashToMsg h)) sighash+ return [insertTapKeySig signature] 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+ -- Only a leaf of the coin is signed: its control block must+ -- commit it to the output key+ | Just outKey <- importXOnlyPubKey ctx program,+ commitsLeaf ctx outKey leaf,+ Just secret <- heldXOnly ((hashOfLeaf `elem`) . (.leafHashes)) leafKey -> do h <- taprootSigHash tx spent i sighash Nothing (Just (hashOfLeaf, 0xffffffff))- return [insertTapScriptSig ctx leafKey hashOfLeaf (TaprootSignature (signSchnorr ctx secret (hashToMsg h)) sighash)]+ signature <- taprootSignature (signSchnorr ctx secret (hashToMsg h)) sighash+ return [insertTapScriptSig ctx leafKey hashOfLeaf signature]+ where+ hashOfLeaf = leafHash leaf.leafVersion leaf.script _ -> return [] let inserts = keySig <> scriptSigs return (foldr ($) m inserts, [sighash | not (null inserts)])@@ -1622,10 +1886,17 @@ 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+-- | The sighash type of an ECDSA signature for an input that asks for+-- none: SIGHASH_ALL, with the fork id flag on a network that has one+defaultEcdsaSigHash :: Network -> SigHash+defaultEcdsaSigHash net+ | isJust net.sigHashForkId = setForkIdFlag sigHashAll+ | otherwise = sigHashAll++-- | The hashes of the keys a script names: the keys a coin of that script+-- is signed with+scriptKeyHashes :: Ctx -> ScriptOutput -> [Hash160]+scriptKeyHashes ctx = \case PayPK public -> [addressHash (marshal ctx public)] PayPKHash h -> [h] PayWitnessPKHash h -> [h]@@ -1640,6 +1911,14 @@ | leaf.leafVersion == tapscriptLeafVersion -> importXOnlyPubKey ctx keyBytes _ -> Nothing +-- | Whether the control block of a leaf script commits the leaf to the+-- output key of the coin+commitsLeaf :: Ctx -> PubKey -> TapLeafScript -> Bool+commitsLeaf ctx outKey leaf = case decodeControlBlock ctx leaf.controlBlock of+ Right (v, internalKey, proof) ->+ verifyScriptPathData ctx outKey (ScriptPathData Nothing [] leaf.script (odd v) (v .&. 0xfe) internalKey proof Nothing)+ Left _ -> False+ -- | The error for a sighash type the signer does not accept notAcceptedSigHash :: SigHash -> String notAcceptedSigHash sighash =@@ -1654,10 +1933,8 @@ 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) $+ 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@@ -1693,6 +1970,51 @@ isFinal :: PsbtMap -> Bool isFinal m = any (`Map.member` m) [Key finalScriptSigType B.empty, Key finalScriptWitnessType B.empty] +{- The coins of a PSBT -}++-- | The coin an input of a PSBT spends, as the PSBT gives it+data InputCoin = InputCoin+ { -- | the output the input spends: its amount and its script+ coin :: !TxOut,+ -- | 'True' when the coin is the output of the previous transaction of+ -- the input, whose id the input names, so that it cannot be made up.+ -- 'False' when the PSBT gives the output alone (PSBT_IN_WITNESS_UTXO):+ -- its amount is then a claim of whoever wrote the PSBT.+ fromPreviousTx :: !Bool+ }+ deriving (Eq, Show, Read, Generic)+ deriving anyclass (NFData)++-- | The coin every input of a PSBT spends, 'Nothing' for an input that+-- carries none. A previous transaction must be the one its input spends+-- and have the output it spends, and a witness output given with it must+-- be that output.+--+-- An amount with 'fromPreviousTx' is checked. Another is only checked by a+-- signature that commits to it: a SegWit version 0 signature commits to+-- the amount of its own coin, and a Taproot signature to the amount of+-- every coin of the transaction. Show a user amounts and a fee from+-- checked coins only, or from a PSBT in which the user signs a Taproot+-- input.+inputCoins :: Psbt -> Either String [Maybe InputCoin]+inputCoins psbt = map (fmap (\(output, witnessOnly) -> InputCoin output (not witnessOnly))) <$> spentOutputs psbt++-- | The fee of the transaction of a PSBT: what its coins hold above what+-- its outputs pay. Every input must carry its previous transaction, so+-- that no amount is a claim ('inputCoins' tells which are). A PSBT whose+-- outputs pay more than its coins hold has no fee.+psbtFee :: Psbt -> Either String Word64+psbtFee psbt = do+ coins <- inputCoins psbt+ amounts <- forM (zip [0 :: Int ..] coins) $ \(i, coinM) -> case coinM of+ Nothing -> Left ("Input " <> show i <> " has no coin")+ Just found+ | found.fromPreviousTx -> Right (toInteger found.coin.value)+ | otherwise -> Left ("Input " <> show i <> ": the coin is given as a witness output only, so its amount is not checked")+ let fee = sum amounts - sum (toInteger . (.value) <$> psbt.unsignedTx.outputs)+ when (fee < 0) $ Left "The outputs pay more than the coins hold"+ return (fromInteger fee)+ -- | 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)]@@ -1711,52 +2033,85 @@ 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)+ output <- previousOutput input parent 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))+ unless (witnessCoin == output) $+ Left "the witness output of the input is not the output of its previous transaction"+ return (Just (output, False))+ (Nothing, Just output) -> return (Just (output, 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+-- | What the scripts of an input say about how its coin is signed+data InputScripts = InputScripts+ { -- | the script the coin is signed against: the script of the coin, or+ -- the witness program its redeem script is when it is nested in P2SH+ program :: ScriptOutput,+ -- | the script that names the keys: the script of the coin, its+ -- redeem script or its witness script. 'Nothing' when the input does+ -- not have the redeem or witness script its coin needs.+ signedScript :: Maybe ScriptOutput,+ -- | why a redeem or witness script of the input does not fit its coin+ mismatch :: Maybe String+ }++-- | The scripts of an input, from the script of its coin and its redeem+-- and witness scripts. A redeem script must hash to the coin, and a+-- witness script to the witness program, as BIP 174 requires of a signer:+-- one that does not is reported in 'mismatch'. 'Nothing' when the script+-- that names the keys is no standard template: the library cannot sign or+-- finalize such an input, and leaves it to others.+inputScripts :: Ctx -> ScriptOutput -> PsbtMap -> Either String (Maybe InputScripts)+inputScripts ctx coinScript m = do+ redeemM <- inputRedeemScript m+ witnessM <- inputWitnessScript m+ let redeemOutM = decodeOutput ctx <$> redeemM+ witnessOutM = decodeOutput ctx <$> witnessM+ -- A SegWit coin, nested in P2SH or not, is signed against its witness+ -- program; any other against its own script+ program = case redeemOutM of+ Just (Right 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- }+ redeemMismatch = case redeemM of+ Just redeem+ | not (isPayScriptHash coinScript) -> Just "the input has a redeem script, but its coin is not pay-to-script-hash"+ | toP2SH redeem /= Right coinScript -> Just "the redeem script of the input does not hash to its coin"+ _ -> Nothing+ witnessMismatch = case witnessM of+ Just witness+ | not (isPayWitnessScriptHash program) -> Just "the input has a witness script, but its coin is not pay-to-witness-script-hash"+ | toP2WSH witness /= Right program -> Just "the witness script of the input does not hash to its witness program"+ _ -> Nothing+ -- The script that names the keys, as it decodes+ naming = case program of+ PayWitnessScriptHash _ -> witnessOutM+ PayScriptHash _ -> redeemOutM+ _ -> Just (Right program)+ return $ case naming of+ Just (Left _) -> Nothing+ _ ->+ Just+ InputScripts+ { program = program,+ signedScript = naming >>= eitherToMaybe,+ mismatch = listToMaybe (catMaybes [redeemMismatch, witnessMismatch])+ } +-- | What an input is signed against, as 'makeSignature' wants it: the+-- script of its coin or its witness program, with the redeem script of a+-- P2SH coin or the witness script of a P2WSH coin+signingInput :: TxIn -> TxOut -> SigHash -> InputScripts -> SigInput+signingInput input output sighash scripts =+ SigInput+ { script = scripts.program,+ value = output.value,+ outpoint = input.outpoint,+ sighash = sighash,+ redeem = case scripts.program of+ PayWitnessScriptHash _ -> scripts.signedScript+ PayScriptHash _ -> scripts.signedScript+ _ -> Nothing+ }+ {- Finalizing -} -- | Finalize every input whose signatures suffice for the coin it spends,@@ -1764,28 +2119,37 @@ -- 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.+--+-- Only the signatures of the keys the script of the coin names are+-- looked at. Each one is verified first, against the digest of the coin,+-- which is computed once per sighash type. A signature that does not+-- verify, or is of an uncompressed key on a SegWit version 0 coin, is+-- refused, as @PSBTInputSignedAndVerified@ and the standard script flags+-- refuse it in Bitcoin Core. Every signature of the input must use the+-- sighash type the input names, and SIGHASH_ALL when it names none+-- (SIGHASH_DEFAULT for a Taproot coin, and with the fork id flag on a+-- network that has one), as Bitcoin Core requires.+-- -- 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.+-- other input stays as it is: one without enough signatures, one whose+-- scripts are no standard template, and a legacy coin known from its+-- witness output only, which Bitcoin Core does not finalize either. 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, _)+ Just (output :: TxOut, witnessOnly) | not (isFinal m),- Right coinScript <- unmarshal ctx coin.script -> do+ Right coinScript <- unmarshal ctx output.script -> do finals <- if isTaprootCoin coinScript then finalTaprootScripts ctx tx allCoins i m coinScript- else finalScripts net ctx tx i input coin coinScript m+ else finalScripts net ctx tx i input output witnessOnly coinScript m return $ case finals of Just (scriptSig, stack) -> foldr ($) (cleared m) $@@ -1800,66 +2164,112 @@ 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+-- | The final script and witness of an input signed with ECDSA, from the+-- partial signatures of the keys its script names, when they suffice.+-- Each of those signatures is verified first.+finalScripts :: Network -> Ctx -> Tx -> Int -> TxIn -> TxOut -> Bool -> ScriptOutput -> PsbtMap -> Either String (Maybe (ByteString, WitnessStack))+finalScripts net ctx tx i input output witnessOnly 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+ scriptsM <- inputScripts ctx coinScript m+ case scriptsM of+ Nothing -> return Nothing+ Just scripts+ -- Only a witness signature may rely on a witness output+ | witnessOnly && not (isSegwit scripts.program) -> return Nothing+ | otherwise -> do+ forM_ scripts.mismatch Left+ let asked = fromMaybe (defaultEcdsaSigHash net) sighashM+ otherType keyBytes =+ Left $+ "the signature of the key " <> quoted keyBytes <> case sighashM of+ Just _ -> " does not use the sighash type of the input"+ Nothing -> " does not use SIGHASH_ALL, and the input names no sighash type"+ sigInput = signingInput input output asked scripts+ named = maybe [] (namedSignatures ctx m) scripts.signedScript+ -- The signatures of the keys the script names: each is a+ -- signature, of the sighash type asked, by a key the coin may be+ -- spent with+ checked <- forM named $ \(public, bytes) -> do+ (sig, sh) <- case decodeTxSig net ctx bytes of+ Right (TxSignature sig sh) -> return (sig, sh)+ _ -> Left ("the signature of the key " <> quoted (marshal ctx public) <> " is not a valid signature")+ unless (sh == asked) $ otherType (marshal ctx public)+ when (isSegwit scripts.program && not public.compress) $+ Left ("the signature of the key " <> quoted (marshal ctx public) <> " is of an uncompressed key, which a SegWit version 0 coin may not be spent with")+ return (public, sig)+ -- Every other signature of the input uses that sighash type too:+ -- its last byte tells, and nothing else of it is looked at+ forM_ [(key, sig) | (key, sig) <- Map.toAscList m, key.keyType == partialSigType] $ \(key, sig) ->+ unless (not (B.null sig) && B.last sig == fromIntegral (asked .&. 0xff)) $ otherType key.keyData+ if null checked+ then return Nothing+ else do+ -- One digest serves every signature, since all use one type+ digest <- makeSigHash net ctx tx i sigInput.script output.value asked sigInput.redeem+ forM_ checked $ \(public, sig) ->+ unless (verifyHashSig ctx digest sig public.point) $+ Left ("the signature of the key " <> quoted (marshal ctx public) <> " does not verify")+ return (assemble scripts named) where- cs' = show . encodeHex+ quoted = show . encodeHex+ -- The final script and witness, from the signatures of the keys the+ -- script names+ assemble :: InputScripts -> [(PublicKey, ByteString)] -> Maybe (ByteString, WitnessStack)+ assemble scripts named = case coinScript of+ PayScriptHash _ -> do+ redeem <- either (const Nothing) id (inputRedeemScript m)+ if isSegwit scripts.program+ then (scriptOf [S.encode redeem],) <$> witnessOf scripts named+ else (\found -> (scriptOf (found <> [S.encode redeem]), [])) <$> (items named =<< scripts.signedScript)+ _+ | isSegwit coinScript -> (B.empty,) <$> witnessOf scripts named+ | otherwise -> (\found -> (scriptOf found, [])) <$> (items named =<< scripts.signedScript)+ -- The witness of a SegWit coin: the items of its program, with the+ -- witness script last for P2WSH+ witnessOf :: InputScripts -> [(PublicKey, ByteString)] -> Maybe [ByteString]+ witnessOf scripts named = case scripts.program of+ PayWitnessScriptHash _ -> do+ witnessScript <- either (const Nothing) id (inputWitnessScript m)+ (<> [S.encode witnessScript]) <$> (items named =<< scripts.signedScript)+ _ -> items named =<< scripts.signedScript+ -- The items a script is spent with: the pushes of an input script,+ -- or the items of a witness+ items :: [(PublicKey, ByteString)] -> ScriptOutput -> Maybe [ByteString]+ items named = \case+ PayPK public -> (: []) <$> lookup public named+ PayPKHash h -> withKey <$> ofHash named h+ PayMulSig keys n -> (B.empty :) <$> enough named keys n+ PayWitnessPKHash h -> withKey <$> ofHash named h+ _ -> Nothing+ ofHash :: [(PublicKey, ByteString)] -> Hash160 -> Maybe (PublicKey, ByteString)+ ofHash named h = listToMaybe [(public, signature) | (public, signature) <- named, addressHash (marshal ctx public) == h]+ enough :: [(PublicKey, ByteString)] -> [PublicKey] -> Int -> Maybe [ByteString]+ enough named keys n = let found = mapMaybe (`lookup` named) keys in if length found >= n then Just (take n found) else Nothing+ withKey :: (PublicKey, ByteString) -> [ByteString]+ withKey (public, signature) = [signature, marshal ctx public]+ scriptOf = S.encode . Script . map opPushData +-- | The partial signatures of an input whose keys the script names, in+-- the order of the map. A key is named by a script of public keys when it+-- is one of them, and by a script of a key hash when it has that hash.+-- The others are not looked at, so their number costs nothing.+namedSignatures :: Ctx -> PsbtMap -> ScriptOutput -> [(PublicKey, ByteString)]+namedSignatures ctx m script =+ [ (public, sig)+ | (key, sig) <- Map.toAscList m,+ key.keyType == partialSigType,+ addressHash key.keyData `elem` named,+ Right public <- [unmarshal ctx key.keyData]+ ]+ where+ named = scriptKeyHashes ctx script+ -- | 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.+-- against the output key and the digest of its leaf. A signature must+-- use the sighash type the input names, and SIGHASH_DEFAULT when it names+-- none, as Bitcoin Core requires. finalTaprootScripts :: Ctx -> Tx -> Maybe [TxOut] -> Int -> PsbtMap -> ScriptOutput -> Either String (Maybe (ByteString, WitnessStack)) finalTaprootScripts ctx tx allCoins i m coinScript = do program <- taprootProgram coinScript@@ -1869,11 +2279,13 @@ 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"+ sameType signature = case sighashM of+ Just asked -> unless (signature.hash == asked) $ Left "a Taproot signature does not use the sighash type of the input"+ Nothing -> unless (signature.hash == sigHashDefault) $ Left "a Taproot signature does not use SIGHASH_DEFAULT, and the input names no sighash type"+ -- Every signature of the input, used or not, as Bitcoin Core checks them+ mapM_ sameType (maybeToList keySigM <> map snd scriptSigs) 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)) $@@ -1889,11 +2301,8 @@ 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) $+ unless (commitsLeaf ctx outKey leaf) $ 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)) $
src/Haskoin/Transaction/Taproot.hs view
@@ -1,10 +1,13 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE NoFieldSelectors #-}@@ -33,6 +36,8 @@ mastCommitment, getMerkleProofs, leafHash,+ leafHashBytes,+ checkTapTree, TapLeaf (..), tapTreeLeaves, tapTreeFromLeaves,@@ -46,6 +51,7 @@ -- * Witnesses TaprootWitness (..), ScriptPathData (..),+ scriptPathScriptBytes, viewTaprootWitness, encodeTaprootWitness, verifyScriptPathData,@@ -54,7 +60,9 @@ decodeControlBlock, -- * Signatures- TaprootSignature (..),+ TaprootSignature,+ pattern TaprootSignature,+ taprootSignature, isTaprootSigHash, taprootSigHash, encodeTaprootSig,@@ -87,10 +95,10 @@ import Data.Bytes.Get (MonadGet, getBytes, runGetS) 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, maybeToList) import Data.Word (Word32, Word8)+import GHC.Records (HasField (..)) import Haskoin.Crypto.Hash import Haskoin.Crypto.Keys.Common import Haskoin.Network.Common (putVarIntBytes)@@ -126,11 +134,12 @@ =<< getBytes 32 instance MarshalJSON Ctx XOnlyPubKey where+ -- The hex must hold the 32 bytes of the key and nothing else. A longer+ -- value, such as a 33-byte compressed key, is another kind of key. unmarshalValue ctx =- withText "XOnlyPubKey" $ either fail pure . (des <=< hex)+ withText "XOnlyPubKey" $ either fail pure . (unmarshal ctx <=< hex) where hex = maybe (Left "Unable to decode hex") Right . decodeHex- des = runGetS $ marshalGet ctx marshalValue ctx = String . encodeHex . marshal ctx@@ -159,6 +168,11 @@ -- subset of the leaves are known. Note that the tree is invariant under swapping -- branches at an internal node. --+-- A leaf holds its script as opcodes. For a leaf whose bytes are not a list+-- of opcodes, put its hash in the tree: @MASTCommitment (leafHashBytes+-- version bytes)@. 'checkTapTree' tells whether every leaf of a tree can be+-- spent.+-- -- @since 0.21.0 data MAST = MASTBranch MAST MAST@@ -166,21 +180,33 @@ | MASTCommitment (Digest SHA256) deriving (Eq, Show) --- | Get the inclusion proofs for the leaves in the tree. The proof is ordered--- leaf-to-root.+-- | The inclusion proof of every leaf of the tree, depth first: the leaf+-- version, the script, and the hashes of the siblings on the way from the+-- leaf up to the root. This is the order a control block holds them in, the+-- one of @taproot_tree_helper@ in BIP 341 and of @TaprootBuilder::Combine@+-- in Bitcoin Core. A leaf at depth @d@ has a proof of @d@ hashes. --+-- A proof of more than 'maxControlBlockNodes' hashes cannot be used: no+-- control block may hold it. 'checkTapTree' tells whether a tree has such+-- a leaf.+-- -- @since 0.21.0 getMerkleProofs :: MAST -> [(TapLeafVersion, Script, [Digest SHA256])]-getMerkleProofs = getProofs mempty+getMerkleProofs = snd . go where- getProofs proof = \case- MASTBranch branchL branchR ->- (updateProof proof (mastCommitment branchR) <$> getMerkleProofs branchL)- <> (updateProof proof (mastCommitment branchL) <$> getMerkleProofs branchR)- MASTLeaf v s -> [(v, s, proof)]- MASTCommitment {} -> mempty- updateProof proofInit branchCommitment (v, s, proofTail) =- (v, s, reverse $ proofInit <> (branchCommitment : proofTail))+ -- The commitment of a subtree with the proofs of its leaves inside+ -- that subtree. Joining two subtrees adds the hash of the sibling at+ -- the end of every proof.+ go = \case+ MASTBranch left right ->+ let (hashL, proofsL) = go left+ (hashR, proofsR) = go right+ in ( hashBranch hashL hashR,+ [(v, s, proof <> [hashR]) | (v, s, proof) <- proofsL]+ <> [(v, s, proof <> [hashL]) | (v, s, proof) <- proofsR]+ )+ MASTLeaf v s -> (leafHash v s, [(v, s, [])])+ MASTCommitment commitment -> (commitment, []) -- | Calculate the root hash for this tree. --@@ -201,19 +227,57 @@ 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.+-- | The hash of a leaf of the tree, the tapleaf hash of BIP 341:+-- @hash_TapLeaf(v || compact_size(size of s) || s)@, the tagged hash of the+-- leaf version and the script with its length as a CompactSize.+--+-- Versions up to 1.5.1 wrote the length as a base-128 varint, which is the+-- same byte below 128 only. For a script of 128 bytes or more they gave a+-- leaf hash, and so an output key and an address, that no other software+-- derives. The changelog of the release after 1.5.1 says how to move coins+-- that sit at such an address. leafHash :: TapLeafVersion -> Script -> Digest SHA256-leafHash leafVersion leafScript =+leafHash leafVersion = leafHashBytes leafVersion . runPutS . serialize++-- | The tapleaf hash of a script given as its bytes, as it is in the+-- witness of a script path spend. Use this for a script that is not a list+-- of opcodes, which a 'Script' cannot hold.+leafHashBytes :: TapLeafVersion -> ByteString -> Digest SHA256+leafHashBytes leafVersion scriptBytes = hashFinalize . hashUpdate (initTaggedHash "TapLeaf") . runPutS $ do- serialize leafVersion- serialize $ VarInt (BS.length scriptBytes)- putByteString scriptBytes+ putWord8 leafVersion+ putVarIntBytes scriptBytes++-- | Whether every leaf of a tree can be spent by its script path, as far as+-- the shape of the tree tells. It fails, with the reason, when:+--+-- * a node is deeper than 'maxControlBlockNodes' (128). The control block+-- of a leaf holds one hash per level, and BIP 341 allows at most 128.+-- Bitcoin Core's @TaprootBuilder@ refuses such a tree.+-- * a leaf version is odd or is @0x50@. A control block carries the leaf+-- version in the bits @0xfe@ of its first byte, so an odd version cannot+-- be expressed, and @0x50@ would read as an annex.+--+-- 'taprootOutputKey' and 'taprootScriptOutput' do not make this check: the+-- output of such a tree is well defined, and its other leaves and its key+-- path can still be spent. Check a tree with this function before you give+-- out its address, since a leaf it refuses can never be spent.+checkTapTree :: MAST -> Either String ()+checkTapTree = go (0 :: Int) where- scriptBytes = runPutS $ serialize leafScript+ go depth node+ | depth > maxControlBlockNodes =+ Left ("checkTapTree: a node is deeper than " <> show maxControlBlockNodes)+ | otherwise = case node of+ MASTBranch left right -> go (depth + 1) left >> go (depth + 1) right+ MASTLeaf version _+ | odd version -> Left ("checkTapTree: the leaf version " <> show version <> " is odd")+ | version == 0x50 -> Left "checkTapTree: the leaf version 0x50 would read as an annex"+ | otherwise -> Right ()+ MASTCommitment _ -> Right () -- | A leaf of a script tree with its depth, as the field -- @PSBT_OUT_TAP_TREE@ of BIP 371 lists the leaves, depth first@@ -226,13 +290,17 @@ -- | 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.+-- not known, and when a leaf is deeper than 'maxControlBlockNodes' (128):+-- such a leaf can never be spent, @PSBT_OUT_TAP_TREE@ cannot hold it, and+-- 'tapTreeFromLeaves' refuses it. tapTreeLeaves :: MAST -> Maybe [TapLeaf]-tapTreeLeaves = go 0+tapTreeLeaves = go (0 :: Int) where- go d = \case- MASTBranch l r -> (<>) <$> go (d + 1) l <*> go (d + 1) r- MASTLeaf v s -> Just [TapLeaf d v s]+ go depth = \case+ MASTBranch l r -> (<>) <$> go (depth + 1) l <*> go (depth + 1) r+ MASTLeaf v s+ | depth > maxControlBlockNodes -> Nothing+ | otherwise -> Just [TapLeaf (fromIntegral depth) v s] MASTCommitment _ -> Nothing -- | The tree whose leaves these are, depth first with their depth, built@@ -272,6 +340,17 @@ -- 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. --+-- Two things this function does not do:+--+-- * It does not look at the shape of the tree. A leaf deeper than 128, or+-- with a leaf version a control block cannot carry, gets an output like+-- any other and can never be spent. Run 'checkTapTree' on the tree first.+-- * It calls 'error' when the tweak is not below the order of the curve or+-- gives the point at infinity, which BIP 341 defines as a failure. The+-- tweak is a SHA256 output, so this happens for about one tree in 2^128,+-- and no input is known that reaches it. 'taprootOutputSecKey' returns+-- 'Nothing' in the same case.+-- -- @since 0.21.0 taprootOutputKey :: Ctx -> TaprootOutput -> PubKey taprootOutputKey ctx TaprootOutput {..} =@@ -303,7 +382,9 @@ . (`hashUpdate` exportXOnlyPubKey ctx internalKey) $ initTaggedHash "TapTweak" --- | Generate the output script for a taproot output+-- | The output script of a Taproot output: witness version 1 and the+-- x-only output key of 'taprootOutputKey', whose two caveats apply here+-- too. Check a script tree with 'checkTapTree' before you pay to it. -- -- @since 0.21.0 taprootScriptOutput :: Ctx -> TaprootOutput -> ScriptOutput@@ -332,6 +413,8 @@ data ScriptPathData = ScriptPathData { annex :: Maybe ByteString, stack :: [ByteString],+ -- | the script as opcodes. It is the empty script when 'opaqueScript'+ -- holds the bytes of a script that is not a list of opcodes. script :: Script, -- | the parity of the y coordinate of the output key extIsOdd :: Bool,@@ -339,10 +422,23 @@ leafVersion :: Word8, internalKey :: PubKey, -- | the Merkle proof of the leaf, leaf to root- control :: [Digest SHA256]+ control :: [Digest SHA256],+ -- | the bytes of the script, when they are not the serialization of a+ -- 'Script'. A script path spend can be valid with such bytes: a+ -- Tapscript that holds an @OP_SUCCESSx@ opcode succeeds even if later+ -- bytes do not decode, and a leaf of a version other than @0xc0@ is not+ -- looked at. 'Nothing' for every script that is a list of opcodes, in+ -- which case 'script' holds it.+ opaqueScript :: Maybe ByteString } deriving (Eq, Show) +-- | The bytes of the script of a script path spend, as they are in the+-- witness: 'opaqueScript' when there is one, the serialization of 'script'+-- otherwise. The leaf hash is taken on these bytes.+scriptPathScriptBytes :: ScriptPathData -> ByteString+scriptPathScriptBytes d = fromMaybe (runPutS (serialize d.script)) d.opaqueScript+ -- | The number of nodes a control block may hold at most, the depth of the -- deepest leaf BIP 341 allows maxControlBlockNodes :: Int@@ -379,7 +475,9 @@ -- | 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.+-- and more are the stack, the script and the control block. The script is+-- read whatever its bytes are, see 'opaqueScript'. 'Nothing' for an empty+-- witness and for a control block that 'decodeControlBlock' refuses. -- -- @since 0.21.0 viewTaprootWitness :: Ctx -> WitnessStack -> Maybe TaprootWitness@@ -387,8 +485,13 @@ 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+ -- The script is kept as opcodes when the bytes are exactly the+ -- serialization of a list of opcodes, and as bytes otherwise, so+ -- that the leaf hash is always the hash of the witness bytes.+ let (script, opaqueScript) = case runGetS deserialize scriptBytes of+ Right parsed | runPutS (serialize parsed) == scriptBytes -> (parsed, Nothing)+ _ -> (Script [], Just scriptBytes) pure $ ScriptPathSpend ScriptPathData@@ -398,33 +501,65 @@ extIsOdd = odd v, leafVersion = v .&. 0xfe, internalKey,- control+ control,+ opaqueScript } [] -> Nothing where (annex, items) = case reverse witnessStack of a : rest- | not (null rest),- Just (0x50, _) <- BS.uncons a ->- (Just a, reverse rest)+ | not (null rest), isAnnex a -> (Just a, reverse rest) _ -> (Nothing, witnessStack) --- | Transform the high-level representation of taproot witness data into a witness stack+-- | The witness stack of a Taproot spend, in the order of BIP 341: the+-- signature of a key path spend, or the stack, the script and the control+-- block of a script path spend, and then the annex when there is one. --+-- It fails for a witness that no node reads back as the same spend:+--+-- * an annex that is empty or does not start with @0x50@, which would be+-- read as a signature, a script or a control block;+-- * a proof of more than 'maxControlBlockNodes' hashes;+-- * a leaf version that is odd or is @0x50@, which a control block cannot+-- carry.+-- -- @since 0.21.0-encodeTaprootWitness :: Ctx -> TaprootWitness -> WitnessStack+encodeTaprootWitness :: Ctx -> TaprootWitness -> Either String WitnessStack encodeTaprootWitness ctx = \case- 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+ KeyPathSpend signature annex -> do+ mapM_ checkAnnex annex+ return (signature : maybeToList annex)+ ScriptPathSpend d -> do+ mapM_ checkAnnex d.annex+ when (length d.control > maxControlBlockNodes) $+ Left ("encodeTaprootWitness: a control block holds at most " <> show maxControlBlockNodes <> " nodes")+ when (odd d.leafVersion || d.leafVersion == annexTag) $+ Left ("encodeTaprootWitness: a control block cannot carry the leaf version " <> show d.leafVersion)+ return $+ d.stack+ <> [ scriptPathScriptBytes d,+ encodeControlBlock ctx (d.leafVersion .|. bool 0 1 d.extIsOdd) d.internalKey d.control+ ]+ <> maybeToList d.annex+ where+ checkAnnex annex =+ unless (isAnnex annex) $ Left "encodeTaprootWitness: an annex starts with 0x50" +-- | The first byte of an annex, @ANNEX_TAG@ in Bitcoin Core+annexTag :: Word8+annexTag = 0x50++-- | Whether bytes can be an annex: BIP 341 defines the annex as a last+-- witness item whose first byte is @0x50@+isAnnex :: ByteString -> Bool+isAnnex bytes = case BS.uncons bytes of+ Just (first, _) -> first == annexTag+ Nothing -> False+ -- | 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.+-- control block says, and the proof has at most 'maxControlBlockNodes'+-- hashes. The leaf hash is taken on 'scriptPathScriptBytes'. -- -- @since 0.21.0 verifyScriptPathData ::@@ -434,9 +569,12 @@ ScriptPathData -> Bool verifyScriptPathData ctx outkey spd = fromMaybe False $ do- tweak commitment- >>= fmap onComputedKey- . tweakAddXOnlyPubKey ctx spd.internalKey+ if length spd.control > maxControlBlockNodes+ then Nothing+ else+ tweak commitment+ >>= fmap onComputedKey+ . tweakAddXOnlyPubKey ctx spd.internalKey where onComputedKey computedKey = XOnlyPubKey outkey == XOnlyPubKey computedKey@@ -444,19 +582,52 @@ commitment = taprootCommitment ctx spd.internalKey (Just merkleRoot) merkleRoot = foldl' hashBranch theLeafHash spd.control- theLeafHash = leafHash (spd.leafVersion .&. 0xfe) spd.script+ theLeafHash = leafHashBytes (spd.leafVersion .&. 0xfe) (scriptPathScriptBytes spd) expectedParity = bool 0 1 spd.extIsOdd {- 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)+-- hash type byte unless it is 'sigHashDefault'.+--+-- The hash type is always one that 'isTaprootSigHash' accepts. Build a+-- value with 'taprootSignature' or 'decodeTaprootSig', which check it, and+-- take it apart with the pattern 'TaprootSignature' or with the fields+-- @sig@ and @hash@, as in @signature.hash@. The fields cannot be updated.+data TaprootSignature = MkTaprootSignature !SchnorrSig !SigHash+ deriving (Eq) +instance Show TaprootSignature where+ showsPrec d (MkTaprootSignature s h) =+ showParen (d > 10) $+ showString "TaprootSignature {sig = "+ . shows s+ . showString ", hash = "+ . shows h+ . showString "}"++instance HasField "sig" TaprootSignature SchnorrSig where+ getField (MkTaprootSignature s _) = s++instance HasField "hash" TaprootSignature SigHash where+ getField (MkTaprootSignature _ h) = h++-- | The signature and the hash type of a 'TaprootSignature'. This pattern+-- only matches. 'taprootSignature' builds a value.+pattern TaprootSignature :: SchnorrSig -> SigHash -> TaprootSignature+pattern TaprootSignature s h <- MkTaprootSignature s h++{-# COMPLETE TaprootSignature #-}++-- | A Taproot signature from a Schnorr signature and its hash type. Fails+-- for a hash type that 'isTaprootSigHash' refuses, such as @0x04@,+-- @0x41@ or a value above one byte: every node refuses such a signature,+-- and its bytes could not say the hash type it was made with.+taprootSignature :: SchnorrSig -> SigHash -> Either String TaprootSignature+taprootSignature s h+ | isTaprootSigHash h = Right (MkTaprootSignature s h)+ | otherwise = Left ("taprootSignature: invalid hash type " <> show (fromIntegral h :: Word32))+ -- | 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@@ -465,23 +636,25 @@ 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'+-- of the signature, then the hash type byte unless it is 'sigHashDefault'.+-- 'decodeTaprootSig' reads these bytes back to the same value, since a+-- 'TaprootSignature' only holds a hash type that Taproot defines. encodeTaprootSig :: TaprootSignature -> ByteString-encodeTaprootSig s- | s.hash == sigHashDefault = s.sig.get- | otherwise = s.sig.get <> BS.singleton (fromIntegral s.hash)+encodeTaprootSig (MkTaprootSignature s h)+ | h == sigHashDefault = s.get+ | otherwise = s.get <> BS.singleton (fromIntegral h) -- | 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 == 64 = (`MkTaprootSignature` 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)+ (`MkTaprootSignature` 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@@ -491,8 +664,10 @@ -- 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.+-- spent outputs do not match the inputs, with @SIGHASH_SINGLE@ and no+-- output at the index of the input, or when the annex is empty or does not+-- start with @0x50@: no node takes such an item for an annex, so no node+-- computes that digest. taprootSigHash :: -- | transaction to sign Tx ->@@ -502,7 +677,7 @@ Int -> -- | what to sign SigHash ->- -- | annex of the input, if there is one+ -- | annex of the input, if there is one, with its @0x50@ prefix Maybe ByteString -> -- | for a script path spend: the leaf hash, and the position of the last -- @OP_CODESEPARATOR@ executed, @0xffffffff@ for none@@ -517,6 +692,8 @@ 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"+ forM_ annexM $ \annex ->+ unless (isAnnex annex) $ Left "taprootSigHash: an annex starts with 0x50" return . taggedHash "TapSighash" . runPutS $ do putWord8 0x00 -- the epoch putWord8 (fromIntegral sh)
src/Haskoin/Util/Arbitrary/Address.hs view
@@ -20,17 +20,21 @@ import Haskoin.Util.Arbitrary.Util import Test.QuickCheck --- | Arbitrary pay-to-public-key-hash or pay-to-script-hash address.+-- | Arbitrary address of a Bitcoin Cash network: pay-to-public-key-hash,+-- pay-to-script-hash with either hash size, a token-aware address, or any+-- other CashAddr kind. arbitraryBitcoinCashAddress :: Gen Address arbitraryBitcoinCashAddress = oneof [ arbitraryPubKeyAddress, arbitraryScriptAddress, arbitraryScript32Address,+ arbitraryTokenAddress, arbitraryCashAddress ] --- | Arbitrary address including pay-to-witness+-- | Arbitrary address of a Bitcoin network, including pay-to-witness and+-- Taproot. arbitraryBitcoinAddress :: Gen Address arbitraryBitcoinAddress = oneof@@ -38,6 +42,7 @@ arbitraryScriptAddress, arbitraryWitnessPubKeyAddress, arbitraryWitnessScriptAddress,+ arbitraryTaprootAddress, arbitraryWitnessAddress ] @@ -80,8 +85,27 @@ Just a -> return a Nothing -> error "Error generating arbitrary WitnessAddress" +-- | Arbitrary Taproot address: witness version 1 with a 32-byte program.+-- 'arbitraryWitnessAddress' almost never draws that combination.+arbitraryTaprootAddress :: Gen Address+arbitraryTaprootAddress = do+ bs <- B.pack <$> vectorOf 32 arbitrary+ case witnessAddress 1 bs of+ Just a -> return a+ Nothing -> error "Error generating arbitrary Taproot address"+ arbitraryScript32Address :: Gen Address arbitraryScript32Address = script32Address <$> arbitraryHash256++-- | Arbitrary token-aware CashAddr that can be paid: type 2 with a 20-byte+-- hash, or type 3 with a 20-byte or a 32-byte hash.+arbitraryTokenAddress :: Gen Address+arbitraryTokenAddress = do+ (ver, len) <- elements [(2, 20), (3, 20), (3, 32)]+ bs <- B.pack <$> vectorOf len arbitrary+ case cashAddress ver bs of+ Just a -> return a+ Nothing -> error "Error generating arbitrary token-aware address" arbitraryCashAddress :: Gen Address arbitraryCashAddress = do
src/Haskoin/Util/Arbitrary/Block.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+ -- | -- Module : Haskoin.Test.Block -- Copyright : No rights reserved@@ -11,20 +14,33 @@ import Haskoin.Block import Haskoin.Crypto (Ctx) import Haskoin.Network.Data+import Haskoin.Transaction.Common (Tx, TxHash (..), txHash) import Haskoin.Util.Arbitrary.Crypto import Haskoin.Util.Arbitrary.Network import Haskoin.Util.Arbitrary.Transaction import Haskoin.Util.Arbitrary.Util import Test.QuickCheck --- | Block full or arbitrary transactions.+-- | Block of arbitrary transactions. The header has the Merkle root of the+-- transactions when there are some. On a network without SegWit the+-- transactions have no witness, as a block of that network has none. arbitraryBlock :: Network -> Ctx -> Gen Block arbitraryBlock net ctx = do h <- arbitraryBlockHeader c <- choose (0, 10)- txs <- vectorOf c (arbitraryTx net ctx)- return $ Block h txs+ txs <- vectorOf c (arbitraryNetworkTx net ctx)+ let header = case buildMerkleRoot (map txHash txs) of+ Right root -> (h :: BlockHeader) {merkle = root}+ Left _ -> h+ return $ Block header txs +-- | Arbitrary transaction of the kind a peer of the network sends: with or+-- without a witness where the network has SegWit, without one elsewhere.+arbitraryNetworkTx :: Network -> Ctx -> Gen Tx+arbitraryNetworkTx net ctx+ | net.segWit = arbitraryTx net ctx+ | otherwise = arbitraryLegacyTx net ctx+ -- | Block header with random hash. arbitraryBlockHeader :: Gen BlockHeader arbitraryBlockHeader =@@ -61,24 +77,49 @@ arbitraryHeaders = Headers <$> listOf1 ((,) <$> arbitraryBlockHeader <*> arbitraryVarInt) --- | Arbitrary 'MerkleBlock' with at least one hash.+-- | Arbitrary 'MerkleBlock' that holds a valid proof: a partial Merkle tree+-- over one to thirty distinct transaction hashes, with any subset of them+-- matching, under a header that has the Merkle root of those hashes. The+-- flags are padded with zero bits to whole bytes, as they are on the wire. arbitraryMerkleBlock :: Gen MerkleBlock arbitraryMerkleBlock = do bh <- arbitraryBlockHeader- ntx <- arbitrary- hashes <- listOf1 arbitraryHash256- c <- choose (1, 10)- flags <- vectorOf (c * 8) arbitrary- return $ MerkleBlock bh ntx hashes flags+ n <- choose (1, 30)+ hashes <- (TxHash <$> arbitraryHash256) `vectorOfDistinct` n+ matches <- vectorOf n arbitrary+ let (flags, partial) = buildPartialMerkle (zip hashes matches)+ padding = (8 - length flags `mod` 8) `mod` 8+ root = either (const bh.merkle) id (buildMerkleRoot hashes)+ return $+ MerkleBlock+ (bh :: BlockHeader) {merkle = root}+ (fromIntegral n)+ partial+ (flags ++ replicate padding False)+ where+ vectorOfDistinct gen k = go k []+ where+ go 0 acc = return acc+ go i acc = do+ x <- gen `suchThat` (`notElem` acc)+ go (i - 1 :: Int) (x : acc) --- | Arbitrary 'BlockNode'+-- | Arbitrary 'BlockNode'. One in ten is at height zero, like a genesis+-- node, whose skip hash is its own hash and is not serialized. arbitraryBlockNode :: Gen BlockNode arbitraryBlockNode =- BlockNode- <$> arbitraryBlockHeader- <*> choose (0, maxBound)- <*> arbitrarySizedNatural- <*> arbitraryBlockHash+ frequency [(1, genesisLike), (9, above)]+ where+ genesisLike = do+ h <- arbitraryBlockHeader+ w <- arbitrarySizedNatural+ return $ BlockNode h 0 w (headerHash h)+ above =+ BlockNode+ <$> arbitraryBlockHeader+ <*> choose (1, maxBound)+ <*> arbitrarySizedNatural+ <*> arbitraryBlockHash -- | Arbitrary 'HeaderMemory' arbitraryHeaderMemory :: Gen HeaderMemory
src/Haskoin/Util/Arbitrary/Keys.hs view
@@ -34,6 +34,13 @@ k <- arbitraryPrivateKey return (k, derivePublicKey ctx k) +-- | Arbitrary private key with its public key, both in compressed form: the+-- only form of key that a SegWit version 0 input is signed with+arbitraryKeyPairC :: Ctx -> Gen (PrivateKey, PublicKey)+arbitraryKeyPairC ctx = do+ k <- wrapSecKey True <$> arbitrary+ return (k, derivePublicKey ctx k)+ arbitraryFingerprint :: Gen Fingerprint arbitraryFingerprint = Fingerprint <$> arbitrary
src/Haskoin/Util/Arbitrary/Message.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+ -- | -- Module : Haskoin.Test.Message -- Copyright : No rights reserved@@ -30,31 +33,45 @@ <*> arbitrary <*> arbitraryCheckSum32 --- | Arbitrary 'SendCompact'.+-- | Arbitrary 'SendCompact', with the versions 1 and 2 of BIP 152 and any+-- other number. arbitrarySendCompact :: Gen SendCompact-arbitrarySendCompact = SendCompact <$> arbitrary <*> elements [1, 2]+arbitrarySendCompact = SendCompact <$> arbitrary <*> oneof [elements [1, 2], arbitrary] --- | Strictly increasing block positions.+-- | Strictly increasing block positions, none above the given one. The+-- first and the last possible position each come up often.+arbitraryPositionsUpTo :: Word32 -> Gen [Word32]+arbitraryPositionsUpTo largest =+ nub . sort <$> listOf (frequency [(1, elements [0, largest]), (8, choose (0, largest))])++-- | Strictly increasing block positions of up to 32 bits. arbitraryPositions :: Gen [Word32]-arbitraryPositions = nub . sort <$> listOf arbitrary+arbitraryPositions = arbitraryPositionsUpTo maxBound --- | Arbitrary 'CompactBlock'.+-- | Arbitrary 'CompactBlock' that a peer of the network can send: positions+-- within the 'maxCompactBlockIndex' of the network, and transactions as+-- 'arbitraryNetworkTx' makes them. arbitraryCompactBlock :: Network -> Ctx -> Gen CompactBlock arbitraryCompactBlock net ctx = do- positions <- arbitraryPositions+ positions <- arbitraryPositionsUpTo net.maxCompactBlockIndex CompactBlock <$> arbitraryBlockHeader <*> arbitrary <*> listOf (choose (0, maxShortId))- <*> mapM (\i -> PrefilledTx i <$> arbitraryTx net ctx) positions+ <*> mapM (\i -> PrefilledTx i <$> arbitraryNetworkTx net ctx) positions --- | Arbitrary 'GetBlockTxn'.+-- | Arbitrary 'GetBlockTxn' with positions of up to 32 bits. arbitraryGetBlockTxn :: Gen GetBlockTxn arbitraryGetBlockTxn = GetBlockTxn <$> arbitraryBlockHash <*> arbitraryPositions --- | Arbitrary 'BlockTxn'.+-- | Arbitrary 'GetBlockTxn' that a peer of the network can send.+arbitraryNetworkGetBlockTxn :: Network -> Gen GetBlockTxn+arbitraryNetworkGetBlockTxn net =+ GetBlockTxn <$> arbitraryBlockHash <*> arbitraryPositionsUpTo net.maxCompactBlockIndex++-- | Arbitrary 'BlockTxn' that a peer of the network can send. arbitraryBlockTxn :: Network -> Ctx -> Gen BlockTxn-arbitraryBlockTxn net ctx = BlockTxn <$> arbitraryBlockHash <*> listOf (arbitraryTx net ctx)+arbitraryBlockTxn net ctx = BlockTxn <$> arbitraryBlockHash <*> listOf (arbitraryNetworkTx net ctx) -- | Arbitrary 'GetCFilters'. arbitraryGetCFilters :: Gen GetCFilters@@ -99,7 +116,7 @@ MNotFound <$> arbitraryNotFound, MGetBlocks <$> arbitraryGetBlocks, MGetHeaders <$> arbitraryGetHeaders,- MTx <$> arbitraryTx net ctx,+ MTx <$> arbitraryNetworkTx net ctx, MBlock <$> arbitraryBlock net ctx, MMerkleBlock <$> arbitraryMerkleBlock, MHeaders <$> arbitraryHeaders,@@ -116,7 +133,7 @@ MFeeFilter <$> arbitraryFeeFilter, MSendCompact <$> arbitrarySendCompact, MCompactBlock <$> arbitraryCompactBlock net ctx,- MGetBlockTxn <$> arbitraryGetBlockTxn,+ MGetBlockTxn <$> arbitraryNetworkGetBlockTxn net, MBlockTxn <$> arbitraryBlockTxn net ctx, MGetCFilters <$> arbitraryGetCFilters, MCFilter <$> arbitraryCFilter,
src/Haskoin/Util/Arbitrary/Network.hs view
@@ -23,7 +23,13 @@ -- | Arbitrary 'VarInt'. arbitraryVarInt :: Gen VarInt-arbitraryVarInt = VarInt <$> arbitrary+arbitraryVarInt =+ VarInt+ <$> frequency+ [ -- the last value of each encoding length and the first of the next+ (1, elements [0, 0xfc, 0xfd, 0xffff, 0x10000, 0xffffffff, 0x100000000, maxBound]),+ (3, arbitrary)+ ] -- | Arbitrary 'VarString'. arbitraryVarString :: Gen VarString@@ -140,7 +146,8 @@ arbitraryReject :: Gen Reject arbitraryReject = Reject- <$> arbitraryMessageCommand+ -- A reject can name any bytes as its command, of any length.+ <$> frequency [(4, arbitraryMessageCommand), (1, stringToCommand <$> arbitraryBS)] <*> arbitraryRejectCode <*> arbitraryVarString <*> arbitraryMaybe arbitraryHash256
src/Haskoin/Util/Arbitrary/Script.hs view
@@ -258,13 +258,45 @@ (10, lst3 <$> arbitraryTxSignature net ctx) ] --- | Arbitrary m of n parameters, with n up to 'maxMultisigKeys'.+-- | Arbitrary m of n parameters, with n up to 'maxMultisigKeys'. Such a+-- multisig fits a bare output or a pay-to-witness-script-hash output. For a+-- pay-to-script-hash output use 'arbitraryP2SHMSParam'. arbitraryMSParam :: Gen (Int, Int) arbitraryMSParam = do m <- choose (1, maxMultisigKeys) n <- choose (m, maxMultisigKeys) return (m, n) +-- | The most keys of a pay-to-script-hash multisig whatever the form of its+-- keys. A redeem script is at most 520 bytes, and an uncompressed key takes+-- 66 of them with its push: 3 + 7 × 66 = 465, and 8 keys would be 531.+maxP2SHMultisigKeys :: Int+maxP2SHMultisigKeys = 7++-- | The most compressed keys of a pay-to-script-hash multisig. A compressed+-- key takes 34 bytes with its push: 3 + 15 × 34 = 513, and 16 keys would be+-- 547.+maxP2SHMultisigKeysC :: Int+maxP2SHMultisigKeysC = 15++-- | Arbitrary m of n parameters of a multisig whose script fits a+-- pay-to-script-hash redeem script with keys of any form: n up to+-- 'maxP2SHMultisigKeys'.+arbitraryP2SHMSParam :: Gen (Int, Int)+arbitraryP2SHMSParam = do+ m <- choose (1, maxP2SHMultisigKeys)+ n <- choose (m, maxP2SHMultisigKeys)+ return (m, n)++-- | Arbitrary m of n parameters of a multisig of compressed keys whose+-- script fits a pay-to-script-hash redeem script: n up to+-- 'maxP2SHMultisigKeysC'.+arbitraryP2SHMSParamC :: Gen (Int, Int)+arbitraryP2SHMSParamC = do+ m <- choose (1, maxP2SHMultisigKeysC)+ n <- choose (m, maxP2SHMultisigKeysC)+ return (m, n)+ -- | Arbitrary 'ScriptOutput' (Can by any valid type). arbitraryScriptOutput :: Network -> Ctx -> Gen ScriptOutput arbitraryScriptOutput net ctx =@@ -275,13 +307,17 @@ arbitrarySHOutput, arbitraryDCOutput ]- ++ if net.segWit- then- [ arbitraryWPKHashOutput,- arbitraryWSHOutput,- arbitraryWitOutput- ]- else []+ ++ ( if net.segWit+ then+ [ arbitraryWPKHashOutput,+ arbitraryWSHOutput,+ arbitraryWitOutput+ ]+ else []+ )+ -- Pay-to-script-hash with a 256-bit hash exists on the networks that+ -- have CashAddr.+ ++ [arbitrarySH32Output | isJust net.cashAddrPrefix] -- | Arbitrary 'ScriptOutput' of type 'PayPK', 'PayPKHash' or 'PayMS' -- (Not 'PayScriptHash', 'DataCarrier', or SegWit)@@ -333,6 +369,32 @@ <$> vectorOf n (arbitraryKeyPair ctx `suchThat` ((.compress) . snd)) return $ PayMulSig keys m +-- | Arbitrary multisig that fits a pay-to-script-hash redeem script: up to+-- 7 keys of any form, or up to 15 compressed keys.+arbitraryP2SHMSOutput :: Ctx -> Gen ScriptOutput+arbitraryP2SHMSOutput ctx =+ oneof+ [ do+ (m, n) <- arbitraryP2SHMSParam+ keys <- map snd <$> vectorOf n (arbitraryKeyPair ctx)+ return $ PayMulSig keys m,+ arbitraryP2SHMSOutputC ctx+ ]++-- | Arbitrary multisig of compressed keys that fits a pay-to-script-hash+-- redeem script: up to 15 keys.+arbitraryP2SHMSOutputC :: Ctx -> Gen ScriptOutput+arbitraryP2SHMSOutputC ctx = do+ (m, n) <- arbitraryP2SHMSParamC+ keys <-+ map snd+ <$> vectorOf n (arbitraryKeyPair ctx `suchThat` ((.compress) . snd))+ return $ PayMulSig keys m++-- | Arbitrary 'ScriptOutput' of type 'PayScript32Hash'.+arbitrarySH32Output :: Gen ScriptOutput+arbitrarySH32Output = PayScript32Hash <$> arbitraryHash256+ -- | Arbitrary 'ScriptOutput' of type 'PayScriptHash'. arbitrarySHOutput :: Gen ScriptOutput arbitrarySHOutput = do@@ -346,16 +408,17 @@ arbitraryDCOutput :: Gen ScriptOutput arbitraryDCOutput = DataCarrier <$> listOf arbitraryBS --- | Arbitrary 'ScriptInput'.+-- | Arbitrary 'ScriptInput'. A nested witness input is only drawn on a+-- network that has SegWit. arbitraryScriptInput :: Network -> Ctx -> Gen ScriptInput arbitraryScriptInput net ctx =- oneof+ oneof $ [ arbitraryPKInput net ctx, arbitraryPKHashInput net ctx, arbitraryMSInput net ctx,- arbitrarySHInput net ctx,- arbitraryNestedWitnessInput+ arbitrarySHInput net ctx ]+ ++ [arbitraryNestedWitnessInput | net.segWit] -- | Arbitrary 'ScriptInput' of type 'NestedWitnessInput', wrapping a -- 'PayWitnessPKHash' or a 'PayWitnessScriptHash'.@@ -422,10 +485,12 @@ ] -- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a--- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.+-- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'. The+-- redeem script is at most 520 bytes long, as a pay-to-script-hash redeem+-- script must be. arbitraryMulSigSHInput :: Network -> Ctx -> Gen ScriptInput arbitraryMulSigSHInput net ctx =- arbitraryMSOutput ctx >>= \case+ arbitraryP2SHMSOutput ctx >>= \case rdm@(PayMulSig _ m) -> do sigs <- vectorOf m (arbitraryTxSignatureEmpty net ctx) return $ ScriptHashInput (SpendMulSig sigs) rdm@@ -435,7 +500,7 @@ -- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'. arbitraryMulSigSHInputC :: Network -> Ctx -> Gen ScriptInput arbitraryMulSigSHInputC net ctx =- arbitraryMSOutputC ctx >>= \case+ arbitraryP2SHMSOutputC ctx >>= \case rdm@(PayMulSig _ m) -> do sigs <- vectorOf m (arbitraryTxSignatureEmpty net ctx) return $ ScriptHashInput (SpendMulSig sigs) rdm@@ -444,7 +509,7 @@ -- | Like 'arbitraryMulSigSHCInput' with no empty signatures. arbitraryMulSigSHInputFull :: Network -> Ctx -> Gen ScriptInput arbitraryMulSigSHInputFull net ctx =- arbitraryMSOutput ctx >>= \case+ arbitraryP2SHMSOutput ctx >>= \case rdm@(PayMulSig _ m) -> do sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net ctx) return $ ScriptHashInput (SpendMulSig sigs) rdm@@ -453,7 +518,7 @@ -- | Like 'arbitraryMulSigSHCInput' with no empty signatures. arbitraryMulSigSHInputFullC :: Network -> Ctx -> Gen ScriptInput arbitraryMulSigSHInputFullC net ctx =- arbitraryMSOutputC ctx >>= \case+ arbitraryP2SHMSOutputC ctx >>= \case rdm@(PayMulSig _ m) -> do sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net ctx) return $ ScriptHashInput (SpendMulSig sigs) rdm
src/Haskoin/Util/Arbitrary/Transaction.hs view
@@ -21,6 +21,7 @@ import Haskoin.Address import Haskoin.Crypto (Ctx) import Haskoin.Crypto.Keys.Common+import Haskoin.Crypto.Keys.Extended (XPubKey (..), listToPath) import Haskoin.Network.Constants import Haskoin.Network.Data import Haskoin.Script@@ -49,20 +50,27 @@ arbitraryTxHash = TxHash <$> arbitraryHash256 -- | Arbitrary amount of Satoshi as 'Word64' (Between 1 and 21e14), spent--- with an arbitrary kind of input+-- with an arbitrary kind of input. A third of the amounts are below 10,000+-- satoshi, near the fees and the dust thresholds, a third below one coin,+-- and a third anywhere up to the 21 million coins. arbitrarySatoshi :: Network -> Gen TestCoin-arbitrarySatoshi net = TestCoin <$> choose (1, net.maxSatoshi) <*> arbitraryInputKind+arbitrarySatoshi net = TestCoin <$> amount <*> arbitraryInputKind+ where+ amount = oneof [choose (1, 10000), choose (1, 100000000), choose (1, net.maxSatoshi)] --- | Arbitrary kind of input, with multisig parameters from--- 'arbitraryMSParam'+-- | Arbitrary kind of input that can exist. A SegWit multisig has+-- parameters from 'arbitraryMSParam', up to 20 keys. A pay-to-script-hash+-- multisig has them from 'arbitraryP2SHMSParamC', up to the 15 compressed+-- keys that fit a redeem script. arbitraryInputKind :: Gen InputKind arbitraryInputKind = do (m, n) <- arbitraryMSParam+ (mP2SH, nP2SH) <- arbitraryP2SHMSParamC elements [ InputPK, InputPKHash, InputUncompressedPKHash,- InputMulSig m n,+ InputMulSig mP2SH nP2SH, InputWitnessPKHash, InputWitnessMulSig m n, InputNestedWitnessPKHash,@@ -176,19 +184,24 @@ return $ TxOut v $ marshal ctx out -- | Arbitrary 'SigInput' with the corresponding private keys used--- to generate the 'ScriptOutput' or 'RedeemScript'. On a network without--- a fork id, it may be a Taproot output spent by its key path.+-- to generate the 'ScriptOutput' or 'RedeemScript'. On a network with+-- SegWit, it may be a SegWit version 0 output, or a Taproot output spent+-- by its key path. The signer refuses those on a network without SegWit. arbitrarySigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey]) arbitrarySigInput net ctx = oneof $ [ wrapKey <$> arbitraryPKSigInput net ctx, wrapKey <$> arbitraryPKHashSigInput net ctx, arbitraryMSSigInput net ctx,- arbitrarySHSigInput net ctx,- wrapKey <$> arbitraryWPKHSigInput net ctx,- arbitraryWSHSigInput net ctx+ arbitrarySHSigInput net ctx ]- <> [wrapKey <$> arbitraryTaprootSigInput net ctx | isNothing net.sigHashForkId]+ <> concat+ [ [ wrapKey <$> arbitraryWPKHSigInput net ctx,+ arbitraryWSHSigInput net ctx,+ wrapKey <$> arbitraryTaprootSigInput net ctx+ ]+ | net.segWit+ ] -- | 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@@ -211,10 +224,16 @@ arbitraryPKHashSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey) arbitraryPKHashSigInput net ctx = arbitraryAnyInput net ctx True --- | Arbitrary 'SigInput'.+-- | Arbitrary 'SigInput' of a key in either form. arbitraryAnyInput :: Network -> Ctx -> Bool -> Gen (SigInput, PrivateKey)-arbitraryAnyInput net ctx pkh = do- (k, p) <- arbitraryKeyPair ctx+arbitraryAnyInput net ctx = arbitraryAnyInputOf (arbitraryKeyPair ctx) net ctx++-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPKHash', when+-- the flag is set, or 'PayPK', of a key from the generator given.+arbitraryAnyInputOf ::+ Gen (PrivateKey, PublicKey) -> Network -> Ctx -> Bool -> Gen (SigInput, PrivateKey)+arbitraryAnyInputOf keyPair net ctx pkh = do+ (k, p) <- keyPair let out | pkh = case addressHash160 (pubKeyAddr ctx p) of Just h -> PayPKHash h@@ -233,9 +252,21 @@ -- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayMulSig'. arbitraryMSSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])-arbitraryMSSigInput net ctx = do- (m, n) <- arbitraryMSParam- ks <- vectorOf n (arbitraryKeyPair ctx)+arbitraryMSSigInput = arbitraryMSSigInputOf arbitraryMSParam++-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayMulSig', whose m+-- of n parameters come from the given generator.+arbitraryMSSigInputOf ::+ Gen (Int, Int) -> Network -> Ctx -> Gen (SigInput, [PrivateKey])+arbitraryMSSigInputOf params net ctx = arbitraryMSSigInputWith (arbitraryKeyPair ctx) params net++-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayMulSig', whose+-- keys and m of n parameters come from the given generators.+arbitraryMSSigInputWith ::+ Gen (PrivateKey, PublicKey) -> Gen (Int, Int) -> Network -> Gen (SigInput, [PrivateKey])+arbitraryMSSigInputWith keyPair params net = do+ (m, n) <- params+ ks <- vectorOf n keyPair let out = PayMulSig (map snd ks) m (val, op, sh) <- arbitraryInputStuff net perm <- choose (0, n - 1)@@ -243,38 +274,45 @@ return (SigInput out val op sh Nothing, ksPerm) -- | Arbitrary 'SigInput' with 'ScriptOutput' of type 'PaySH' and a--- 'RedeemScript'.+-- 'RedeemScript'. A multisig redeem script has at most 7 keys, so that it+-- fits the 520 bytes of a pay-to-script-hash redeem script with keys of any+-- form. arbitrarySHSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey]) arbitrarySHSigInput net ctx = do (SigInput rdm val op sh _, ks) <- oneof [ wrapKey <$> arbitraryPKSigInput net ctx, wrapKey <$> arbitraryPKHashSigInput net ctx,- arbitraryMSSigInput net ctx+ arbitraryMSSigInputOf arbitraryP2SHMSParam net ctx ]- let out = case addressHash160 (payToScriptAddress ctx rdm) of+ let out = case either (const Nothing) addressHash160 (payToScriptAddress ctx rdm) of Just h -> PayScriptHash h Nothing -> error "Could not get Hash160 from address" return (SigInput out val op sh $ Just rdm, ks) +-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayWitnessPKHash'.+-- The key is compressed, as nodes require of a SegWit version 0 input. arbitraryWPKHSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey) arbitraryWPKHSigInput net ctx = do- (k, p) <- arbitraryKeyPair ctx+ (k, p) <- arbitraryKeyPairC ctx (val, op, sh) <- arbitraryInputStuff net let out = case addressHash160 (pubKeyAddr ctx p) of Just h -> PayWitnessPKHash h Nothing -> error "Could not get Hash160 from address" return (SigInput out val op sh Nothing, k) +-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type+-- 'PayWitnessScriptHash' and its witness script. The keys of the witness+-- script are compressed, as nodes require of a SegWit version 0 input. arbitraryWSHSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey]) arbitraryWSHSigInput net ctx = do (SigInput rdm val op sh _, ks) <- oneof- [ wrapKey <$> arbitraryPKSigInput net ctx,- wrapKey <$> arbitraryPKHashSigInput net ctx,- arbitraryMSSigInput net ctx+ [ wrapKey <$> arbitraryAnyInputOf (arbitraryKeyPairC ctx) net ctx False,+ wrapKey <$> arbitraryAnyInputOf (arbitraryKeyPairC ctx) net ctx True,+ arbitraryMSSigInputWith (arbitraryKeyPairC ctx) arbitraryMSParam net ]- let out = case addressHash256 (payToWitnessScriptAddress ctx rdm) of+ let out = case either (const Nothing) addressHash256 (payToWitnessScriptAddress ctx rdm) of Just h -> PayWitnessScriptHash h Nothing -> error "Could not get Hash256 from address" return (SigInput out val op sh $ Just rdm, ks)@@ -351,7 +389,9 @@ return . fromRight (error "Could not decode transaction") $ signTx net ctx tx [sigi] [prv] arbitraryData = do- (m, n) <- arbitraryMSParam+ -- At most 7 keys, so that the script also fits a pay-to-script-hash+ -- redeem script+ (m, n) <- arbitraryP2SHMSParam val <- getTestCoin <$> arbitrarySatoshi net nPrv <- choose (m, n) keys <- vectorOf n (arbitraryKeyPair ctx)@@ -362,7 +402,7 @@ let h = fromMaybe (error "Could not get Hash160 from address")- (addressHash160 (payToScriptAddress ctx so))+ (either (const Nothing) addressHash160 (payToScriptAddress ctx so)) elements [ (so, val, Nothing, prvKeys, m, n), ( PayScriptHash h,@@ -379,18 +419,32 @@ -- | 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.+-- partial signatures, sighash types, scripts, final scripts, derivation+-- records, extended public keys, the Taproot fields, proprietary fields,+-- and keys the spec does not define. An input that carries a previous+-- transaction spends an output of it, as 'decodePsbt' requires. One PSBT+-- in ten has no input. 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+ drawn <- arbitraryTx net ctx+ inputs0 <- frequency [(9, return drawn.inputs), (1, return [])]+ parents <- mapM (const (oneof [return Nothing, Just <$> spendable])) inputs0+ let spend input = maybe input (\(parent, index) -> TxIn (OutPoint (txHash parent) index) input.script input.sequence)+ base = psbtOf (Tx drawn.version (zipWith spend inputs0 parents) drawn.outputs [] drawn.locktime)+ globals <- globalFields+ inputs <- mapM inputFields parents outputs <- mapM (const outputFields) base.outputs let version0 = Psbt Version0 base.unsignedTx globals inputs outputs oneof [return version0, version2 version0] where+ -- A transaction with at least one output, and the index of one of them+ spendable = do+ parent <- arbitraryTx net ctx+ extra <- arbitraryTxOut net ctx+ let outputs = if null parent.outputs then [extra] else parent.outputs+ index <- choose (0, length outputs - 1)+ return (Tx parent.version parent.inputs outputs parent.witness parent.locktime, fromIntegral index) -- 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@@ -413,22 +467,44 @@ _ -> 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]))+ -- Types from 0x20 to 0x7f are none the spec defines for any map. A+ -- type from 253 on is written over three bytes: 0xfd, then the type+ -- over two bytes, little-endian, which are the start of the key data.+ unknownFields = Map.fromList <$> resize 3 (listOf (oneof [unknownField, wideTypeField, proprietaryField])) unknownField = (,) <$> (Key <$> choose (0x20, 0x7f) <*> arbitraryBS) <*> arbitraryBS+ wideTypeField = do+ wide <- choose (0xfd, 0xffff :: Int)+ rest <- arbitraryBS+ (,) (Key 0xfd (BS.pack [fromIntegral (wide `mod` 256), fromIntegral (wide `div` 256)] <> rest)) <$> arbitraryBS proprietaryField = (,) <$> (proprietaryKey <$> (Proprietary <$> arbitraryBS <*> arbitrary <*> arbitraryBS)) <*> arbitraryBS- inputFields = do+ globalFields = do m <- unknownFields+ xpubs <- resize 2 (listOf xpubRecord)+ return (foldr ($) m xpubs)+ -- The path of an extended key has one index per level of its depth+ xpubRecord = do+ xpub <- arbitraryXPubKey ctx+ path <- listToPath <$> vectorOf (fromIntegral xpub.depth) arbitrary+ (\fingerprint -> insertXpubRecord net ctx xpub (Derivation fingerprint path)) <$> arbitraryFingerprint+ inputFields parentM = do+ m <- unknownFields fields <- sequence- [ maybeField (insertNonWitnessUtxo <$> arbitraryTx net ctx),+ [ return (maybe id (insertNonWitnessUtxo . fst) parentM), 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)+ derivations (insertInputDerivation ctx),+ partialSignatures,+ maybeField (insertInputTapInternalKey ctx <$> point),+ maybeField (insertInputTapMerkleRoot <$> digest),+ maybeField (insertTapKeySig <$> arbitraryTapSig),+ maybeField (insertTapScriptSig ctx <$> point <*> digest <*> arbitraryTapSig),+ maybeField (insertTapLeafScript <$> leafScript),+ maybeField (insertInputTapDerivation ctx <$> point <*> tapDerivation) ] return (foldr ($) m fields) outputFields = do@@ -437,10 +513,36 @@ sequence [ maybeField (insertOutputRedeemScript <$> arbitraryScript), maybeField (insertOutputWitnessScript <$> arbitraryScript),- derivations (insertOutputDerivation ctx)+ derivations (insertOutputDerivation ctx),+ maybeField (insertOutputTapInternalKey ctx <$> point),+ maybeField (insertOutputTapTree <$> scriptTree),+ maybeField (insertOutputTapDerivation ctx <$> point <*> tapDerivation) ] 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+ -- Signatures of a sighash type the network defines, under keys that+ -- need not be theirs: a PSBT is read without verifying them+ partialSignatures = do+ signatures <- resize 2 (listOf ((,) <$> (snd <$> arbitraryKeyPair ctx) <*> ((\(_, _, signature) -> encodeTxSig net ctx signature) <$> arbitraryTxSignature net ctx)))+ return $ foldr (\(key, signature) inserted -> insertPartialSignature ctx key signature . inserted) id signatures+ point = (.point) . snd <$> arbitraryKeyPair ctx+ digest = leafHash 0xc0 <$> arbitraryScript+ -- 64 bytes, alone or with one of the six sighash bytes of BIP 341+ arbitraryTapSig = do+ bytes <- arbitraryBSn 64+ sighash <- elements [[], [0x01], [0x02], [0x03], [0x81], [0x82], [0x83]]+ return (fromRight (error "arbitraryPsbt: Taproot signature") (decodeTaprootSig (bytes <> BS.pack sighash)))+ leafScript = do+ internal <- point+ nodes <- resize 3 (listOf digest)+ (\script -> TapLeafScript (encodeControlBlock ctx 0xc0 internal nodes) script 0xc0) <$> arbitraryScript+ tapDerivation = TapDerivation <$> resize 2 (listOf digest) <*> (Derivation <$> arbitraryFingerprint <*> arbitraryDerivPath)+ -- The leaves of a whole tree: one leaf alone, or two under the root+ scriptTree =+ oneof+ [ (\script -> [TapLeaf 0 0xc0 script]) <$> arbitraryScript,+ (\a b -> [TapLeaf 1 0xc0 a, TapLeaf 1 0xc0 b]) <$> arbitraryScript <*> arbitraryScript+ ]
src/Haskoin/Util/Helpers.hs view
@@ -251,13 +251,19 @@ } -- | Convert from one power-of-two base to another, as long as it fits in a--- '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.+-- 'Word'. With padding, leftover bits fill one more output digit. 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.+--+-- The conversion also fails for a digit that does not fit the width it is+-- converted from, as the reference code of BIP 173 does, and for widths that+-- are not positive or whose sum is above the 64 bits of a 'Word'. convertBits :: Bool -> Int -> Int -> [Word] -> Maybe [Word] convertBits pad frombits tobits i+ | frombits < 1 || tobits < 1 || frombits + tobits > 64 = Nothing+ | any (>= 1 `shiftL` frombits) i = Nothing | pad = Just . reverse $ if xbits == 0 then xout else leftover : xout | xbits >= frombits = Nothing | leftover /= 0 = Nothing@@ -325,16 +331,50 @@ lo = fromIntegral (minBound :: Int32) hi = fromIntegral (maxBound :: Int32) +-- | Read an 'Integer' that 'putInteger' wrote. A number has one encoding, and+-- any other way to write it is refused:+--+-- * the tag byte is 0x00 for a number that fits 32 bits, and 0x01 otherwise+-- * the sign byte of the wide form is 0x01, or 0xff for a negative number+-- * the bytes of the wide form are the shortest that hold the number, so the+-- last one is not zero+--+-- This is the encoding that 'putInteger' has always written, so stored data+-- reads as before. The bytes are read in one piece and turned into a number+-- in close to linear time, so a long number cannot stall the reader. getInteger :: (MonadGet m) => m Integer getInteger = getWord8 >>= \case- 0 -> fromIntegral <$> getInt32be- _ -> do- sign <- getWord8- bytes <- getList getWord8- let v = roll bytes- return $! if sign == 0x01 then v else -v+ 0x00 -> fromIntegral <$> getInt32be+ 0x01 -> do+ sign <-+ getWord8 >>= \case+ 0x01 -> return 1+ 0xff -> return (-1)+ _ -> fail "getInteger: the sign byte is not 0x01 or 0xff"+ bytes <- getLengthBytes+ let n = sign * littleEndianToInteger bytes+ when (B.null bytes || B.last bytes == 0x00 || fitsInt32 n) $+ fail "getInteger: the number is not in its shortest form"+ return n+ _ -> fail "getInteger: the tag byte is not 0x00 or 0x01"+ where+ fitsInt32 n =+ n >= fromIntegral (minBound :: Int32) && n <= fromIntegral (maxBound :: Int32) +-- | The number that a string of bytes spells, least significant byte first.+-- The string is split in halves and the two numbers are joined. Adding one+-- byte at a time would move the whole number at every step, which takes time+-- that grows with the square of the length.+littleEndianToInteger :: ByteString -> Integer+littleEndianToInteger bs+ | B.length bs <= 8 = B.foldr (\byte n -> n `shiftL` 8 .|. toInteger byte) 0 bs+ | otherwise = low .|. (high `shiftL` (8 * B.length lowBytes))+ where+ (lowBytes, highBytes) = B.splitAt (B.length bs `div` 2) bs+ low = littleEndianToInteger lowBytes+ high = littleEndianToInteger highBytes+ putMaybe :: (MonadPut m) => (a -> m ()) -> Maybe a -> m () putMaybe f Nothing = putWord8 0x00 putMaybe f (Just x) = putWord8 0x01 >> f x@@ -372,11 +412,6 @@ step 0 = Nothing step i = Just (fromIntegral i, i `shiftR` 8) -roll :: (Integral a, Bits a) => [Word8] -> a-roll = foldr unstep 0- where- unstep b a = a `shiftL` 8 .|. fromIntegral b- nrBits :: (Ord a, Integral a) => a -> Int nrBits k = let expMax = until (\e -> 2 ^ e > k) (* 2) 1@@ -389,9 +424,17 @@ mid = (lo + hi) `div` 2 in findNr (expMax `div` 2) expMax --- | Read as a list of pairs of int and element.+-- | Read a map as a count and that many pairs of key and element. The keys+-- must come in ascending order, each one once, which is how 'putIntMap'+-- writes them. A key given twice is refused. It used to be read, and the+-- second element silently replaced the first. getIntMap :: (MonadGet m) => m Int -> m a -> m (IntMap a)-getIntMap i m = IntMap.fromList <$> getList (getTwo i m)+getIntMap i m = do+ pairs <- getList (getTwo i m)+ let keys = map fst pairs+ unless (and (zipWith (<) keys (drop 1 keys))) $+ fail "getIntMap: the keys are not in ascending order"+ return (IntMap.fromDistinctAscList pairs) putIntMap :: (MonadPut m) => (Int -> m ()) -> (a -> m ()) -> IntMap a -> m () putIntMap f g = putList (putTwo f g) . IntMap.toAscList
src/Haskoin/Util/Marshal.hs view
@@ -25,11 +25,24 @@ marshalLazy :: (Marshal s a) => s -> a -> Lazy.ByteString marshalLazy s = runPutL . marshalPut s +-- | Read a value from bytes that hold the value and nothing else. Bytes left+-- over after the value are an error, so that two different byte strings never+-- read as the same value. To read a value from the start of a longer stream,+-- run 'marshalGet' yourself. unmarshal :: (Marshal s a) => s -> ByteString -> Either String a-unmarshal = runGetS . marshalGet+unmarshal s = runGetS (marshalGetAll s) +-- | Like 'unmarshal', for lazy bytes. unmarshalLazy :: (Marshal s a) => s -> Lazy.ByteString -> Either String a-unmarshalLazy s = runGetS (marshalGet s) . Lazy.toStrict+unmarshalLazy s = runGetS (marshalGetAll s) . Lazy.toStrict++-- | Read a value and require that the input ends there.+marshalGetAll :: (Marshal s a, MonadGet m) => s -> m a+marshalGetAll s = do+ value <- marshalGet s+ done <- isEmpty+ unless done $ fail "unmarshal: bytes left over after the value"+ return value class MarshalJSON s a | a -> s where marshalValue :: s -> a -> Value
test/Haskoin/Address/Bech32Spec.hs view
@@ -33,6 +33,12 @@ forM_ validAddresses testValidAddress it "rejects the BIP-173 and BIP-350 invalid address vectors" $ forM_ invalidAddresses testInvalidAddress+ it "rejects an uppercase address with a look-alike of an uppercase letter" $+ -- U+212A KELVIN SIGN in place of K. Its lowercase form is the letter k,+ -- so only a check for ASCII refuses it. The address is the first+ -- valid vector of BIP 173.+ segwitDecode "bc" "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7\x212AV8F3T4"+ `shouldBe` Nothing it "rejects an address with a non-ASCII look-alike character" $ -- U+017F in place of s segwitDecode "tb" "tb1qrp33g0q5c5tx\x017Fp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"@@ -80,10 +86,22 @@ 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 "refuses a five-bit word above 31 instead of raising an error" $+ -- UnsafeWord5 can hold any byte. Only 0 to 31 have a character.+ bech32Encode Bech32 "bc" [UnsafeWord5 99] `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+ it "rejects a string longer than 90 characters" $ do+ -- The vectors of BIP 173 and BIP 350 with a valid checksum and 91+ -- characters, so that only the length can refuse them+ isNothing (bech32Decode "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx")+ `shouldBe` True+ isNothing (bech32Decode "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4")+ `shouldBe` True+ it "accepts a string of exactly 90 characters" $+ -- The valid vector of BIP 173 with an 83-character human-readable part+ (fmap (\(enc, hrp, _) -> (enc, Text.length hrp)) (bech32Decode "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"))+ `shouldBe` Just (Bech32, 83) describe "base conversion" $ do it "toBase32 splits bytes into five-bit groups and pads with zero bits" $ -- 11111111 becomes 11111 and 11100@@ -170,6 +188,9 @@ ( Bech32, "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w" ),+ ( Bech32,+ "?1ezyfcl"+ ), ( Bech32m, "A1LQFN3A" ),@@ -202,7 +223,23 @@ "1pzry9x0s0muk", "x1b4n0q5v", "li1dgmt3",- "de1lg7wt\xFF"+ "de1lg7wt\xFF",+ -- The rest of the invalid strings of BIP 173+ "A1G7SGD8",+ "10a06t8",+ "1qzzfhee",+ -- The invalid strings of BIP 350+ "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",+ "qyrz8wqd2c9m",+ "1qyrz8wqd2c9m",+ "y1b0jsk6g",+ "lt1igcx5c0",+ "in1muywd",+ "mm1crxm3i",+ "au1s5cgom",+ "M1VUXWEZ",+ "16plkw9",+ "1p2gdwpf" ] validAddresses :: [(Text, Text)]@@ -266,7 +303,12 @@ "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",- "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"+ "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",+ -- The three addresses of witness version 1 and above that BIP 173 lists+ -- as valid. They use the Bech32 checksum, and BIP 350 made them invalid.+ "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",+ "BC1SW50QA3JX3S",+ "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj" ] hrpCaseVariants :: [Text]
test/Haskoin/Address/CashAddrSpec.hs view
@@ -3,6 +3,7 @@ module Haskoin.Address.CashAddrSpec (spec) where +import Control.Exception (evaluate) import Control.Monad import Data.ByteString (ByteString) import Data.ByteString qualified as B@@ -10,9 +11,11 @@ import Data.Maybe import Data.String.Conversions import Data.Text (Text)+import Data.Text qualified as T import Haskoin.Address import Haskoin.Network.Constants import Haskoin.Util+import System.Timeout (timeout) import Test.HUnit import Test.Hspec @@ -151,6 +154,26 @@ it "rejects the prefix of another network" $ cashAddrDecode bch "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap" `shouldBe` Nothing+ it "rejects another prefix in front of the data of a valid address" $+ -- The data and the checksum are those of the mainnet address, so only+ -- the comparison of the prefix text can refuse it+ cashAddrDecode bch "xyz:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+ `shouldBe` Nothing+ it "rejects a hash that is longer than its version byte says" $+ -- Version byte 0x00 says 20 bytes, and 24 follow. The checksum is+ -- valid (Python transcription of the Bitcoin Cash Node encoder).+ cashAddrDecode bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvqsyqcy689j2373"+ `shouldBe` Nothing+ it "decodes the longest address, with a 64-byte hash" $+ -- 11 characters of prefix, the colon, 104 of data and 8 of checksum+ cashAddrDecode bch "bitcoincash:quqqzqsrqszsvpcgpy9qkrqdpc83qygjzv2p29shrqv35xcur50p7gppyg3jgffxyu5zj23t9skjutesxyerxdp4xcmnswf68v7r603lr3frdfp5"+ `shouldBe` Just (0, B.pack [0 .. 63])+ it "rejects a string one character longer than the longest address" $+ cashAddrDecode bch "bitcoincash:quqqzqsrqszsvpcgpy9qkrqdpc83qygjzv2p29shrqv35xcur50p7gppyg3jgffxyu5zj23t9skjutesxyerxdp4xcmnswf68v7r603lr3frdfp5q"+ `shouldBe` Nothing+ it "rejects a string of megabytes at once" $+ timeout 2000000 (evaluate (cashAddrDecode bch ("bitcoincash:" <> T.replicate 20000000 "q")))+ `shouldReturn` Just Nothing it "rejects an address of another network given without prefix" $ cashAddrDecode bch "qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap" `shouldBe` Nothing@@ -190,9 +213,26 @@ 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 "prefix" "" `shouldBe` Just "prefix:x64nx6hz" cash32encode "bitcoincash" (B.cons 0 hash20)- `shouldBe` "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+ `shouldBe` Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"+ it "cash32encode writes an uppercase prefix in lowercase, so that the string is of one case" $+ -- 21 zero bytes+ cash32encode "BitcoinCash" (B.replicate 21 0)+ `shouldBe` Just "bitcoincash:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfnhks603"+ it "cash32encode refuses a prefix that is not made of letters" $ do+ cash32encode "" "" `shouldBe` Nothing+ cash32encode "pre1fix" "" `shouldBe` Nothing+ cash32encode "a:b" "" `shouldBe` Nothing+ cash32encode "pre-fix" "" `shouldBe` Nothing+ -- U+212A KELVIN SIGN lowers to the letter k+ cash32encode "\x212A" "" `shouldBe` Nothing+ it "cash32decode refuses a prefix with a digit, a second colon or another character" $ do+ -- Each string has a valid checksum for its prefix. Bitcoin Cash Node+ -- refuses all three.+ cash32decode "pre1fix:fe8gx7xv" `shouldBe` Nothing+ cash32decode "a:b:wdr5stq6" `shouldBe` Nothing+ cash32decode "pre-fix:c0c507qj" `shouldBe` Nothing it "cash32decode needs a prefix" $ do cash32decode "x64nx6hz" `shouldBe` Nothing cash32decode ":x64nx6hz" `shouldBe` Nothing
test/Haskoin/AddressSpec.hs view
@@ -26,6 +26,7 @@ import Haskoin.Util import Haskoin.Util.Arbitrary import System.Timeout (timeout)+import Text.Read (readMaybe) import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck@@ -52,10 +53,19 @@ prop "textToAddr . addrToText identity" $ forAll arbitraryNetAddress $ \(net, a) -> (textToAddr net =<< addrToText net a) `shouldBe` Just a- prop "outputAddress . addressToOutput identity" $+ prop "an address of a network is the address of its own output script on that network" $+ -- A generic CashAddr is left out. A token-aware one pays the script of+ -- its plain form, and the others have no script. Both have a test below. forAll arbitraryNetAddress $ \(net, a) -> not (isCashAddress a) ==>- (outputAddress ctx =<< addressToOutput a) `shouldBe` Just a+ (scriptToAddressBS net ctx =<< maybeToEither "no script" (addressToScriptBS ctx a))+ `shouldBe` Right a+ prop "an address that outputAddress gives always has a text form on that network" $+ -- The output is of a kind of any network, not only of the one asked+ forAll ((,) <$> arbitraryNetwork <*> (arbitraryNetwork >>= \other -> arbitraryScriptOutput other ctx)) $ \(net, out) ->+ case outputAddress net ctx out of+ Nothing -> property True+ Just a -> property (isJust (addrToText net a)) describe "Address vectors" $ do it "Passes Base58 vectors 1" $ mapM_ testVector vectors@@ -242,6 +252,20 @@ textToAddr btc "b9qhCWwvLw8SYQykKDevkTSApBZZqXzeV" `shouldBe` Nothing it "rejects surrounding whitespace" $ textToAddr btc " 1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\n" `shouldBe` Nothing+ it "reads a CashAddr kind that has no output script, which is not payable" $ do+ -- type 4 with the 20-byte hash 01 … 01: Bitcoin Cash Node gives it no+ -- destination. Encoded with a Python transcription of its encoder.+ let unknownKind = textToAddr bch "bitcoincash:yqqszqgpqyqszqgpqyqszqgpqyqszqgpqyds8d3dsw"+ (isCashAddress <$> unknownKind) `shouldBe` Just True+ (addressToOutput =<< unknownKind) `shouldBe` Nothing+ it "reads the longest CashAddr, with a 64-byte hash" $+ -- 11 characters of prefix, the colon, and 112 of data and checksum+ (addressBytes <$> textToAddr bch longestCashAddr) `shouldBe` Just (B.pack [0 .. 63])+ it "refuses a string longer than any CashAddr without decoding it" $ do+ -- One character more than the longest address: 125+ textToAddr bch (longestCashAddr <> "q") `shouldBe` Nothing+ timeout 2000000 (evaluate (textToAddr bch ("bitcoincash:" <> T.replicate 20000000 "q")))+ `shouldReturn` Just Nothing it "rejects a long string of Base58 characters without decoding it" $ timeout 2000000 (evaluate (textToAddr btc (T.replicate 500000 "z"))) `shouldReturn` Just Nothing@@ -269,18 +293,82 @@ 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"+ (addrToText btc <$> payToScriptAddress ctx (multisig ctx))+ `shouldBe` Right (Just "38fEX6RbBBMmpu3nbbuULku1xyrrzqqqnE") it "payToWitnessScriptAddress hashes the output script with SHA256" $- addrToText btc (payToWitnessScriptAddress ctx (multisig ctx))- `shouldBe` Just "bc1qd6e6c86xp56gw8pty8suuqhscpttea2c5m2fggzjkxzk5nl9faks5pehxu"+ (addrToText btc <$> payToWitnessScriptAddress ctx (multisig ctx))+ `shouldBe` Right (Just "bc1qd6e6c86xp56gw8pty8suuqhscpttea2c5m2fggzjkxzk5nl9faks5pehxu") it "payToNestedScriptAddress wraps the P2WSH script in P2SH" $- addrToText btc (payToNestedScriptAddress ctx (multisig ctx))- `shouldBe` Just "3JSuZ22mPR2aD5bf77KsGi36V5ar3p87LX"+ (addrToText btc <$> payToNestedScriptAddress ctx (multisig ctx))+ `shouldBe` Right (Just "3JSuZ22mPR2aD5bf77KsGi36V5ar3p87LX")+ it "p2shP2wpkhAddr is the P2SH address of the witness program of a key hash" $+ -- The P2SH-P2WPKH example of BIP 143: redeem script 0014 79091972…,+ -- script hash 4733f37cf4db86fbc2efed2500b4f4e49f312023.+ addrToText btc (p2shP2wpkhAddr "79091972186c449eb1ded22b78e40d009bdf0089")+ `shouldBe` Just "38BW8nqpHSWpkf5sXrQd2xYwvnPJwP59ic"+ describe "Script size limits of script hash addresses" $ do+ -- The expected addresses were computed with Python (hashlib, embit,+ -- base58) from the script bytes, not with this library.+ it "gives the P2SH address of a 1-of-15 multisig of compressed keys" $+ -- 3 + 15 × 34 = 513 bytes, which fits the 520 bytes of a push+ (addrToText btc <$> payToScriptAddress ctx (multisigOf ctx 15))+ `shouldBe` Right (Just "32vPTRtH3XPKgBxBtEoiQdGzVEZydYKduQ")+ it "has no P2SH address for a 1-of-16 multisig of compressed keys" $+ -- 3 + 16 × 34 = 547 bytes: no input can push that redeem script+ payToScriptAddress ctx (multisigOf ctx 16)+ `shouldBe` Left "The redeem script is 547 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ it "has no P2SH address for a 1-of-20 multisig of compressed keys" $+ -- 3 + 20 × 34 + 1 = 684 bytes: the count 20 takes a one-byte push+ payToScriptAddress ctx (multisigOf ctx 20)+ `shouldBe` Left "The redeem script is 684 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ it "gives the P2SH address of a redeem script of exactly 520 bytes" $+ -- OP_RETURN, the three bytes of OP_PUSHDATA2 and its length, 516 bytes+ (addrToText btc <$> payToScriptAddress ctx (DataCarrier [B.replicate 516 0xab]))+ `shouldBe` Right (Just "35xAkh7KgcziTpgBWPAoXGzGDgLujKCnLL")+ it "has no P2SH address for a redeem script of 521 bytes" $+ payToScriptAddress ctx (DataCarrier [B.replicate 517 0xab])+ `shouldBe` Left "The redeem script is 521 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ it "gives the P2WSH and the P2SH-P2WSH address of a 1-of-20 multisig" $ do+ (addrToText btc <$> payToWitnessScriptAddress ctx (multisigOf ctx 20))+ `shouldBe` Right (Just "bc1q5yfkferfc5w4t3chd0ghw6ume447rey75v8df4z6sjrhnegv50ysg5v7a5")+ (addrToText btc <$> payToNestedScriptAddress ctx (multisigOf ctx 20))+ `shouldBe` Right (Just "3KHeFzKKQySyJSVTdqkkxxaMGRUCNdPfay")+ it "gives the P2WSH and the P2SH-P2WSH address of a witness script of exactly 10,000 bytes" $ do+ -- OP_RETURN, the three bytes of OP_PUSHDATA2 and its length, 9996 bytes+ (addrToText btc <$> payToWitnessScriptAddress ctx (DataCarrier [B.replicate 9996 0xab]))+ `shouldBe` Right (Just "bc1q5w9futq5wnymeh5wfrak2388u0cmup9nhkwxsq56rraaxvfy22pqrzp44n")+ (addrToText btc <$> payToNestedScriptAddress ctx (DataCarrier [B.replicate 9996 0xab]))+ `shouldBe` Right (Just "34v1ZTik6F9PmGBTcXnmbHF1NfTjufPgvv")+ it "has no P2WSH and no P2SH-P2WSH address for a witness script of 10,001 bytes" $ do+ payToWitnessScriptAddress ctx (DataCarrier [B.replicate 9997 0xab])+ `shouldBe` Left "The witness script is 10001 bytes long, and a pay-to-witness-script-hash witness script can have at most 10000"+ payToNestedScriptAddress ctx (DataCarrier [B.replicate 9997 0xab])+ `shouldBe` Left "The witness script is 10001 bytes long, and a pay-to-witness-script-hash witness script can have at most 10000"+ it "gives an error value, not an exception, for a multisig that no script can express" $ do+ payToScriptAddress ctx (PayMulSig [compressedKey ctx, secondKey ctx] 3)+ `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys"+ payToScriptAddress ctx (PayMulSig [compressedKey ctx] 0)+ `shouldBe` Left "encodeOutput: multisig must require at least one signature"+ payToWitnessScriptAddress ctx (PayMulSig [] 1)+ `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys"+ payToNestedScriptAddress ctx (PayMulSig (replicate 21 (compressedKey ctx)) 1)+ `shouldBe` Left "encodeOutput: multisig has more than 20 keys" describe "addressToOutput, addressToScript and addressToScriptBS" $ do it "builds the locking script of every address kind" $- forM_ scriptTable $ \(a, hex) ->+ forM_ scriptTable $ \(_, a, hex) -> assertEqual (T.unpack hex) (Just hex) (encodeHex <$> addressToScriptBS ctx a)+ it "pays a token-aware address and its plain form with the same script" $ do+ let script = Just "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"+ (encodeHex <$> (addressToScriptBS ctx =<< textToAddr bch "bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w"))+ `shouldBe` script+ (encodeHex <$> (addressToScriptBS ctx =<< textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"))+ `shouldBe` script+ it "the script of a token-aware address has the plain address, which is another value" $ do+ -- An index keyed by address must look a token-aware address up by its+ -- script, or by its plain form+ (scriptToAddressBS bch ctx =<< maybeToEither "no script" (addressToScriptBS ctx tokenP2pkh))+ `shouldBe` Right p2pkh+ tokenP2pkh `shouldNotBe` p2pkh it "pays a token-aware P2PKH address with the plain P2PKH script" $ (encodeHex <$> addressToScriptBS ctx tokenP2pkh) `shouldBe` Just "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"@@ -303,25 +391,55 @@ ((.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 "recovers the address of every standard locking script on a network that has its kind" $+ forM_ scriptTable $ \(net, a, hex) ->+ assertEqual (T.unpack hex) (Right a) (scriptToAddressBS net ctx (fromJust (decodeHex hex))) it "scriptToAddress works on the script AST" $- (scriptToAddress ctx =<< runGetS deserialize (fromJust (decodeHex "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac")))+ (scriptToAddress btc 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))+ scriptToAddressBS btc ctx (fromJust (decodeHex p2pkScript)) `shouldBe` Left "Could not decode address" it "has no address for a bare multisig script" $- scriptToAddressBS ctx (fromJust (decodeHex multisigScript))+ scriptToAddressBS btc ctx (fromJust (decodeHex multisigScript)) `shouldBe` Left "Could not decode address" it "has no address for a data carrier script" $- scriptToAddressBS ctx (fromJust (decodeHex "6a04deadbeef"))+ scriptToAddressBS btc 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+ scriptToAddressBS btc 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+ outputAddress btc ctx (PayPK (compressedKey ctx)) `shouldBe` Nothing+ it "a script of the 256-bit script hash shape has no address on Bitcoin" $ do+ -- Output 0 of mainnet transaction a4bfa8ab…bc31b, in block 211997.+ -- Bitcoin Core reports it as non-standard, with no address.+ let puzzle = fromJust (decodeHex "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087")+ forM_ [btc, btcTest, btcTest4, btcRegTest] $ \net ->+ assertEqual net.name (Left "Could not decode address") (scriptToAddressBS net ctx puzzle)+ -- On Bitcoin Cash the same bytes are pay-to-script-hash with a 32-byte hash+ (addrToText bch =<< eitherToMaybe (scriptToAddressBS bch ctx puzzle))+ `shouldBe` Just "bitcoincash:pdh79rq2kmcmxukp563ydtnr7a8ex85rvhs45zyudrtpjqqqqqqqqgss5hn0j"+ it "a witness program has no address on Bitcoin Cash" $+ forM_ [bch, bchTest, bchTest4, bchRegTest] $ \net -> do+ -- version 0 with 20 bytes, version 0 with 32 bytes, and Taproot+ assertEqual net.name (Left "Could not decode address") $+ scriptToAddressBS net ctx (fromJust (decodeHex "001476a04053bda0a88bda5177b86a15c3b29f559873"))+ assertEqual net.name (Left "Could not decode address") $+ scriptToAddressBS net ctx (fromJust (decodeHex "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"))+ assertEqual net.name (Left "Could not decode address") $+ scriptToAddressBS net ctx (fromJust (decodeHex "51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"))+ it "outputAddress gives an output kind an address only where the network has that kind" $ do+ outputAddress btc ctx (PayScript32Hash hash32) `shouldBe` Nothing+ outputAddress bch ctx (PayScript32Hash hash32) `shouldBe` Just p2sh32+ outputAddress bch ctx (PayWitnessPKHash hash20) `shouldBe` Nothing+ outputAddress bch ctx (PayWitnessScriptHash hash32) `shouldBe` Nothing+ outputAddress bch ctx (PayWitness 1 hash32Bytes) `shouldBe` Nothing+ outputAddress btc ctx (PayWitnessPKHash hash20) `shouldBe` Just p2wpkh+ outputAddress btc ctx (PayWitness 1 hash32Bytes) `shouldBe` Just p2tr+ it "the address of the mainnet 256-bit hash lock script is not written as JSON, because it has none" $+ -- This is the value an indexer encodes for that output+ marshalValue btc <$> eitherToMaybe (scriptToAddressBS btc ctx (fromJust (decodeHex "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087")))+ `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))))@@ -329,6 +447,24 @@ 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 hash of the redeem script bytes of a decoded P2SH input" $ do+ -- OP_0, an empty signature, and the push of the 37-byte redeem script+ -- 51 21 <key of private key 1> 51 ae. Its HASH160 is+ -- 83eebb7d79aa1d388e3b0ac65b98ac580c4da01a (Python, hashlib).+ let script = fromJust (decodeHex "00002551210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179851ae")+ (addrToText btc =<< inputAddress ctx =<< eitherToMaybe (decodeInput btc ctx =<< runGetS deserialize script))+ `shouldBe` Just "3DicS6C8JZm59RsrgXr56iVHzYdQngiehV"+ it "gives no address for a P2SH input whose redeem script pushes its key with OP_PUSHDATA1" $ do+ -- The redeem script 51 4c 21 <key> 51 ae hashes to+ -- 6b5837f2dea4723d69dc0911013387692b970cfa, the address+ -- 3BUbubs2pJzko1Xg5KBVDpoRa7V3kRAsVP. Written back in the form of this+ -- library it would hash to 3DicS6C8…, an address that held no coin.+ let script = fromJust (decodeHex "000026514c210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179851ae")+ (inputAddress ctx =<< eitherToMaybe (decodeInput btc ctx =<< runGetS deserialize script))+ `shouldBe` Nothing+ it "has no address for a redeem script that is too long to be pushed" $+ inputAddress ctx (ScriptHashInput (SpendMulSig [TxSignatureEmpty]) (multisigOf ctx 16))+ `shouldBe` Nothing 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.@@ -354,6 +490,13 @@ 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 declared length above the longest payload before reading the payload" $ do+ -- 41 for a witness program, whose longest is 40+ deserializeAddress ("0401" <> "0000000000000029" <> T.replicate 41 "01")+ `shouldBe` Left "Failed reading: Address payload too long\nEmpty call stack\n"+ -- 65 for a CashAddr hash, whose longest is 64+ deserializeAddress ("0600" <> "0000000000000041" <> T.replicate 65 "01")+ `shouldBe` Left "Failed reading: Address payload too long\nEmpty call stack\n" 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" $@@ -377,6 +520,29 @@ 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+ describe "Read" $ do+ it "reads what Show writes for the generic kinds" $ do+ read (show p2tr) `shouldBe` p2tr+ read (show tokenP2pkh) `shouldBe` tokenP2pkh+ it "does not read a witness version above 16" $+ (readMaybe "OtherWitnessAddress {version = 99, bytes = \"ab\"}" :: Maybe Address)+ `shouldBe` Nothing+ it "does not read a witness program shorter than two bytes" $+ (readMaybe "OtherWitnessAddress {version = 1, bytes = \"\"}" :: Maybe Address)+ `shouldBe` Nothing+ it "does not read a CashAddr type above 15" $+ (readMaybe "OtherCashAddress {version = 200, bytes = \"abcdefghijklmnopqrst\"}" :: Maybe Address)+ `shouldBe` Nothing+ it "does not read a CashAddr hash of a length the standard does not allow" $+ (readMaybe "OtherCashAddress {version = 2, bytes = \"abc\"}" :: Maybe Address)+ `shouldBe` Nothing+ it "does not read a generic kind that has a dedicated constructor" $ do+ -- version 0 with 20 bytes is a WitnessPubKeyAddress+ (readMaybe "OtherWitnessAddress {version = 0, bytes = \"abcdefghijklmnopqrst\"}" :: Maybe Address)+ `shouldBe` Nothing+ -- CashAddr type 0 with 20 bytes is a PubKeyAddress+ (readMaybe "OtherCashAddress {version = 0, bytes = \"abcdefghijklmnopqrst\"}" :: Maybe Address)+ `shouldBe` Nothing {- Fixtures -} @@ -473,17 +639,29 @@ (bchTest, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a") ] --- | Locking script of every address kind that has one.-scriptTable :: [(Address, Text)]+-- | Locking script of every address kind that has one, with a network that+-- has that kind of output.+scriptTable :: [(Network, Address, Text)] scriptTable =- [ (p2pkh, "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"),- (p2sh, "a91476a04053bda0a88bda5177b86a15c3b29f55987387"),- (p2sh32, "aa201863143c14c5166804bd19203356da136c985678cd4d27a1b8c632960490326287"),- (p2wpkh, "001476a04053bda0a88bda5177b86a15c3b29f559873"),- (p2wsh, "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),- (p2tr, "51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),- (witness16, "6028" <> "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728")+ [ (btc, p2pkh, "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"),+ (bch, p2pkh, "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"),+ (btc, p2sh, "a91476a04053bda0a88bda5177b86a15c3b29f55987387"),+ (bch, p2sh, "a91476a04053bda0a88bda5177b86a15c3b29f55987387"),+ (bch, p2sh32, "aa201863143c14c5166804bd19203356da136c985678cd4d27a1b8c632960490326287"),+ (btc, p2wpkh, "001476a04053bda0a88bda5177b86a15c3b29f559873"),+ (btc, p2wsh, "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),+ (btc, p2tr, "51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),+ (btc, witness16, "6028" <> "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728") ]++-- | The longest CashAddr: type 0 with the 64-byte hash 00 01 … 3f. Encoded+-- with a Python transcription of the Bitcoin Cash Node encoder.+longestCashAddr :: Text+longestCashAddr = "bitcoincash:quqqzqsrqszsvpcgpy9qkrqdpc83qygjzv2p29shrqv35xcur50p7gppyg3jgffxyu5zj23t9skjutesxyerxdp4xcmnswf68v7r603lr3frdfp5"++-- | A 1-of-n multisig of the compressed public key of private key 1.+multisigOf :: Ctx -> Int -> ScriptOutput+multisigOf ctx n = PayMulSig (replicate n (compressedKey ctx)) 1 -- | Public key of private key 1, in both forms. compressedKey, uncompressedKey :: Ctx -> PublicKey
test/Haskoin/BlockSpec.hs view
@@ -18,7 +18,8 @@ import Data.Bytes.Serial (deserialize, serialize) import Data.Default (def) import Data.Either (isLeft)-import Data.List (isPrefixOf)+import Data.Hashable (hash)+import Data.List (isInfixOf, isPrefixOf) import Data.Maybe (fromJust) import Data.String (fromString) import Data.String.Conversions (cs)@@ -239,6 +240,13 @@ asertSpec checkpointSpec merkleSpec+ versionRuleSpec+ knownHeaderSpec+ merkleLimitSpec+ chainConstantsSpec+ activationSpec+ minimumWorkSpec+ coreProofSpec describe "block subsidy" $ do it "computes bitcoin block subsidy correctly" (testSubsidy btc) it "computes regtest block subsidy correctly" (testSubsidy btcRegTest)@@ -305,7 +313,7 @@ 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" $+ it "refuses 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@@ -317,10 +325,15 @@ -- 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 headers message that announces 2001 headers, before it reads them" $+ -- the count 2001 = 0x07d1 as a three-byte compact size, and no header.+ -- MAX_HEADERS_RESULTS is 2000 in Bitcoin Core and Bitcoin Cash Node.+ (runGetS deserialize (B.pack [0xfd, 0xd1, 0x07]) :: Either String Headers)+ `leftWith` "too many"+ it "accepts the count of a headers message of 2000 headers" $+ -- the count is accepted, and the parse then fails on the missing headers+ (runGetS deserialize (B.pack [0xfd, 0xd0, 0x07]) :: Either String Headers)+ `leftWith` "too few bytes" 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@@ -410,6 +423,22 @@ withChain btc (getBlockHeader genesisHash) `shouldBe` Just (genesisNode btc) it "finds nothing for an unknown hash" $ withChain btc (getBlockHeader "0000000000000000000000000000000000000000000000000000000000000001") `shouldBe` Nothing+ it "finds nothing for a hash that only shares its first eight bytes with a block" $+ -- the last eight bytes of this display form are those of the genesis hash+ withChain btc (getBlockHeader "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa72b3f1b60a8ce26f") `shouldBe` Nothing+ it "reads a stored node whose chain work fits 256 bits, and no other" $ do+ -- chain work is a 256-bit unsigned number in Bitcoin Core+ let stored w = runPutS (serialize (genesisNode btc :: BlockNode) {work = w})+ readWork w = fmap (.work) (runGetS deserialize (stored w) :: Either String BlockNode)+ readWork (2 ^ (256 :: Int) - 1)+ `shouldBe` Right 115792089237316195423570985008687907853269984665640564039457584007913129639935+ readWork (2 ^ (256 :: Int)) `leftWith` "chain work out of range"+ readWork (-1) `leftWith` "chain work out of range"+ it "hashes two nodes with the same header to the same value, as they are equal" $ do+ let a = (genesisNode btc :: BlockNode) {work = 1}+ b = (genesisNode btc :: BlockNode) {work = 2}+ a == b `shouldBe` True+ hash a == hash b `shouldBe` True 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))@@ -471,10 +500,12 @@ 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+ it "keeps the first block on equal work, whatever the heights" $ do+ -- CBlockIndexWorkComparator in Bitcoin Core compares the chain work and+ -- then the order of arrival. Height is not part of the rule. 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+ (chooseBest (node 10 1) (node 10 2)).height `shouldBe` 1+ (chooseBest (node 10 2) (node 10 1)).height `shouldBe` 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@@ -1007,6 +1038,10 @@ encodeMerkleFlags [] `shouldBe` [] it "splits a list in chunks" $ splitIn 3 [1 .. 7 :: Int] `shouldBe` [[1, 2, 3], [4, 5, 6], [7]]+ it "gives the whole list as one chunk for a chunk length below one" $ do+ splitIn 0 [1, 2, 3 :: Int] `shouldBe` [[1, 2, 3]]+ splitIn (-1) [1, 2, 3 :: Int] `shouldBe` [[1, 2, 3]]+ splitIn 0 ([] :: [Int]) `shouldBe` [] it "packs bools in a byte" $ do boolsToWord8 [] `shouldBe` 0 boolsToWord8 [True] `shouldBe` 1@@ -1032,9 +1067,22 @@ 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+ calcHash 0 2 txids `shouldBe` Just (txids !! 2).get+ calcHash 1 1 txids `shouldBe` Just rightNode+ calcHash 2 0 txids `shouldBe` Just root+ it "has no node outside the tree" $ do+ -- four transactions: levels of 4, 2 and 1 nodes+ calcHash 0 4 txids `shouldBe` Nothing+ calcHash 3 0 txids `shouldBe` Nothing+ calcHash (-1) 0 txids `shouldBe` Nothing+ calcHash 0 (-1) txids `shouldBe` Nothing+ calcHash 0 0 [] `shouldBe` Nothing+ it "gives the position of each match, as Bitcoin Core does" $ do+ extractIndexedMatches btc [True, True, False, True, False, False, False, False] [(txids !! 0).get, (txids !! 1).get, rightNode] 4+ `shouldBe` Right (root, [(1, txids !! 1)])+ -- every transaction matches: positions 0 to 3+ fmap (map fst . snd) (extractIndexedMatches btc (replicate 7 True ++ [False]) (map (.get) txids) 4)+ `shouldBe` Right [0, 1, 2, 3] 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)@@ -1389,3 +1437,291 @@ (t - anchor_parent_time) (h - anchor_height) `shouldBe` g++-- | The result must be a failure whose message holds the given words.+leftWith :: Either String a -> String -> Expectation+leftWith result msg =+ case result of+ Left e | msg `isInfixOf` e -> return ()+ Left e -> expectationFailure ("expected a failure mentioning " ++ show msg ++ ", got " ++ show e)+ Right _ -> expectationFailure ("expected a failure mentioning " ++ show msg ++ ", got a value")++versionRuleSpec :: Spec+versionRuleSpec = describe "block version rule, three independent conditions" $ do+ -- ContextualCheckBlockHeader in Bitcoin Core and Bitcoin Cash Node rejects+ -- when (version < 2 and height >= BIP34) or (version < 3 and height >=+ -- BIP66) or (version < 4 and height >= BIP65).+ it "refuses version 1 from the BIP66 height of bitcoin cash regtest, 1251" $ do+ -- BIP34 is at 100000000 there, far above BIP66 at 1251 and BIP65 at 1351+ validVersion bchRegTest 1250 1 `shouldBe` True+ validVersion bchRegTest 1251 1 `shouldBe` False+ validVersion bchRegTest 1251 2 `shouldBe` False+ validVersion bchRegTest 1251 3 `shouldBe` True+ it "refuses version 3 from the BIP65 height of bitcoin cash regtest, 1351" $ do+ validVersion bchRegTest 1350 3 `shouldBe` True+ validVersion bchRegTest 1351 3 `shouldBe` False+ validVersion bchRegTest 1351 4 `shouldBe` True+ it "refuses a negative version from the BIP66 height of bitcoin cash regtest" $+ validVersion bchRegTest 1251 0xffffffff `shouldBe` False+ it "refuses versions 2 and 3 at height 3 of bitcoin cash testnet4, where BIP65 comes before BIP66" $ do+ -- BIP34 at 2, BIP65 at 3, BIP66 at 4+ validVersion bchTest4 1 1 `shouldBe` True+ validVersion bchTest4 2 1 `shouldBe` False+ validVersion bchTest4 2 2 `shouldBe` True+ validVersion bchTest4 3 2 `shouldBe` False+ validVersion bchTest4 3 3 `shouldBe` False+ validVersion bchTest4 3 4 `shouldBe` True+ it "refuses a version 1 header at height 1252 of bitcoin cash regtest through connectBlock" $+ let net = bchRegTest+ result = withChain net $ do+ chain net net.genesisHeader 1251+ best <- getBestBlockHeader+ let hdr = mineBlock net 0 (headerWithVersion 1 (headerOn best.header (best.header.timestamp + 600) 0x207fffff))+ connectHeight net (best.header.timestamp + 1200) hdr+ in result `shouldBe` Left "Invalid block version: 1"++knownHeaderSpec :: Spec+knownHeaderSpec = describe "headers that are already in the store" $ do+ -- AcceptBlockHeader in Bitcoin Core returns the block index it already has+ -- for a known header, before any other check.+ let base = btcRegTest+ t0 = base.genesisHeader.timestamp+ b1 = mineOn base base.genesisHeader (t0 + 600) 0x207fffff+ b2 = mineOn base b1 (t0 + 1200) 0x207fffff+ b3 = mineOn base b2 (t0 + 1800) 0x207fffff+ -- regtest with a checkpoint on block 2+ net = (base :: Network) {checkpoints = [(2, headerHash b2)]}+ it "returns the stored node for a known header at or below a checkpoint that was reached" $+ withChain net (connectAll net [b1, b2, b3] >> connectHeight net myTime b1) `shouldBe` Right 1+ it "returns the stored nodes for a known batch, and keeps the best block" $+ let (heights, best) = withChain net $ do+ connectAll net [b1, b2, b3]+ r <- connectBlocks net myTime [b1, b2]+ bb <- getBestBlockHeader+ return (fmap (map (.height)) r, bb.height)+ in (heights, best) `shouldBe` (Right [2, 1], 3)+ it "still refuses another header at the height of a checkpoint that was reached" $+ let other = mineBlock base 1 (headerOn b1 (t0 + 1260) 0x207fffff)+ in withChain net (connectAll net [b1, b2, b3] >> connectHeight net myTime other)+ `shouldBe` Left "Rewriting pre-checkpoint chain: 2"++merkleLimitSpec :: Spec+merkleLimitSpec = describe "merkle block size limits" $ do+ -- A proof visits at most one tree node per flag bit. Every node it visits+ -- lies above a stored hash, so it uses at most (height + 1) bits per hash,+ -- and no more bits than the tree has nodes. CPartialMerkleTree in Bitcoin+ -- Core refuses more hashes than transactions and unused flag bytes.+ let header = runPutS (serialize btc.genesisHeader)+ hashA = runPutS (serialize txA.get)+ one = B.pack [1, 0, 0, 0]+ parseMerkle bytes = runGetS deserialize bytes :: Either String MerkleBlock+ it "refuses a merkle block of one transaction with 1000 flag bytes" $+ -- 0xfde803 is the count 1000+ parseMerkle (header <> one <> B.pack [1] <> hashA <> B.pack [0xfd, 0xe8, 0x03] <> B.replicate 1000 1)+ `leftWith` "flag"+ it "refuses a merkle block of one transaction with two flag bytes" $+ -- one transaction is one node, one flag bit, one byte+ parseMerkle (header <> one <> B.pack [1] <> hashA <> B.pack [2, 1, 0]) `leftWith` "flag"+ it "reads a merkle block of one transaction with one flag byte" $+ fmap (\m -> (m.txn, length m.hashes, length m.flags)) (parseMerkle (header <> one <> B.pack [1] <> hashA <> B.pack [1, 1]))+ `shouldBe` Right (1, 1, 8)+ it "refuses a merkle block with more hashes than transactions" $+ parseMerkle (header <> one <> B.pack [2] <> hashA <> hashA <> B.pack [1, 1]) `leftWith` "hashes"+ it "reads the proof of the second transaction of block 100000" $+ -- four transactions, three hashes, the five flag bits 1 1 0 1 0 in one byte: 0x0b+ let hashes =+ map+ (runPutS . serialize . (.get) . displayTxHash)+ [ "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",+ "fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4",+ "8e30899078ca1813be036a073bbf80b86cdddde1c96e9e9c99e9e3782df4ae49"+ ]+ bytes = header <> B.pack [4, 0, 0, 0] <> B.pack [3] <> B.concat hashes <> B.pack [1, 0x0b]+ in fmap (\m -> extractMatches btc m.flags m.hashes (fromIntegral m.txn)) (parseMerkle bytes)+ `shouldBe` Right+ ( Right+ ( (displayTxHash "f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766").get,+ [displayTxHash "fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4"]+ )+ )+ it "refuses a proof of four transactions and three hashes with two flag bytes" $+ -- three hashes under a tree of height 2 use at most 3 * 3 = 9 bits, but+ -- the tree has only 7 nodes: one byte+ let bytes = header <> B.pack [4, 0, 0, 0] <> B.pack [3] <> B.concat (replicate 3 hashA) <> B.pack [2, 0x0b, 0]+ in parseMerkle bytes `leftWith` "flag"+ it "refuses a proof of one hash with three flag bytes under a tree of 1000 transactions" $+ -- 1000 transactions make a tree of height 10. One hash lies under at+ -- most 11 nodes, so it uses at most 11 flag bits: two bytes.+ let thousand = B.pack [0xe8, 0x03, 0, 0]+ in do+ parseMerkle (header <> thousand <> B.pack [1] <> hashA <> B.pack [3, 0, 0, 0]) `leftWith` "flag"+ fmap (\m -> length m.flags) (parseMerkle (header <> thousand <> B.pack [1] <> hashA <> B.pack [2, 0, 0]))+ `shouldBe` Right 16+ it "allows 33 flag bits per hash at most, and no more bits than the tree has nodes" $ do+ -- 7 transactions: widths 7, 4, 2, 1, so 14 nodes and a height of 3+ maxMerkleFlagBits 7 1 `shouldBe` 4+ maxMerkleFlagBits 7 3 `shouldBe` 12+ maxMerkleFlagBits 7 4 `shouldBe` 14+ maxMerkleFlagBits 7 7 `shouldBe` 14+ maxMerkleFlagBits 1 1 `shouldBe` 1+ maxMerkleFlagBits 0 0 `shouldBe` 0+ -- 2^32 - 1 transactions make a tree of height 32+ maxMerkleFlagBits 4294967295 2 `shouldBe` 66+ it "accepts a proof over 16666 transactions and refuses 16667 on bitcoin" $ do+ -- 1000000 bytes of block / 60 bytes per transaction = 16666+ extractMatches btc [False] [txA.get] 16666 `shouldBe` Right (txA.get, [])+ extractMatches btc [False] [txA.get] 16667 `shouldBe` Left "extractMatches: number of transactions excessively high"++chainConstantsSpec :: Spec+chainConstantsSpec = describe "chain parameters of the reference nodes" $ do+ it "retargets every 144 blocks on bitcoin regtest, where the timespan is one day" $ do+ -- kernel/chainparams.cpp of Bitcoin Core: nPowTargetTimespan = 24 * 60 * 60+ btcRegTest.targetTimespan `shouldBe` 86400+ diffInterval btcRegTest `shouldBe` 144+ it "retargets every 2016 blocks on the other networks" $+ map diffInterval [btc, btcTest, btcTest4, bch, bchTest, bchTest4, bchRegTest] `shouldBe` replicate 7 2016+ it "starts the EDA and the DAA one block after the heights of Bitcoin Cash Node" $ do+ -- chainparams.cpp of Bitcoin Cash Node: uahfHeight 478558, daaHeight+ -- 504031 on mainnet; 1155875 and 1188697 on testnet3; 6 and 3000 on testnet4+ (bch.edaHeight, bch.daaHeight) `shouldBe` (Just 478559, Just 504032)+ (bchTest.edaHeight, bchTest.daaHeight) `shouldBe` (Just 1155876, Just 1188698)+ (bchTest4.edaHeight, bchTest4.daaHeight) `shouldBe` (Just 7, Just 3001)+ it "activates ASERT at the time of Bitcoin Cash Node, with its half-lives" $ do+ -- axionActivationTime = 1605441600; nASERTHalfLife is two days on+ -- mainnet (172800 seconds) and one hour on the test networks (3600)+ map (.asertActivationTime) [bch, bchTest, bchTest4, bchRegTest] `shouldBe` replicate 4 (Just 1605441600)+ map (.asertHalfLife) [bch, bchTest, bchTest4, bchRegTest] `shouldBe` [172800, 3600, 3600, 172800]+ it "halves the subsidy every 210000 blocks, and every 150 on regtest" $ do+ map (.halvingInterval) [btc, btcTest, btcTest4, bch, bchTest, bchTest4] `shouldBe` replicate 6 210000+ map (.halvingInterval) [btcRegTest, bchRegTest] `shouldBe` [150, 150]+ it "pays 50 coins before block 210000 and 25 from it" $ do+ computeSubsidy btc 209999 `shouldBe` 5000000000+ computeSubsidy btc 210000 `shouldBe` 2500000000+ computeSubsidy btc 420000 `shouldBe` 1250000000+ computeSubsidy btcRegTest 149 `shouldBe` 5000000000+ computeSubsidy btcRegTest 150 `shouldBe` 2500000000+ it "pays one satoshi until block 6929999 and nothing from block 6930000" $ do+ -- 33 halvings: 5000000000 shifted right by 32 is 1, by 33 is 0+ computeSubsidy btc 6929999 `shouldBe` 1+ computeSubsidy btc 6930000 `shouldBe` 0+ it "has the checkpoint of block 33333 on bitcoin" $ do+ validCP btc 33333 "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6" `shouldBe` True+ validCP btc 33333 "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a7" `shouldBe` False+ it "enforces BIP94 on bitcoin testnet4 only" $ do+ map (.bip94) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+ `shouldBe` [False, False, True, False, False, False, False, False]+ -- the first block of a period may be at most 600 seconds before its parent+ validTimeWarp btcTest4 2016 10000 9400 `shouldBe` True+ validTimeWarp btcTest4 2016 10000 9399 `shouldBe` False+ validTimeWarp btc 2016 10000 9399 `shouldBe` True+ it "computes the four retargets of the pow_tests of Bitcoin Core" $ do+ let next lastTime lastBits firstTime =+ calcNextWork btc (btc.genesisHeader :: BlockHeader) {timestamp = lastTime, bits = lastBits} firstTime+ -- blocks 30240 to 32255, the first difficulty change+ next 1262152739 0x1d00ffff 1261130161 `shouldBe` 0x1d00d86a+ -- blocks 0 to 2015, capped at the proof-of-work limit+ next 1233061996 0x1d00ffff 1231006505 `shouldBe` 0x1d00ffff+ -- blocks 66528 to 68543, limited to a quarter of the timespan+ next 1279297671 0x1c05a3f4 1279008237 `shouldBe` 0x1c0168fd+ -- limited to four times the timespan+ next 1269211443 0x1c387f6f 1263163443 `shouldBe` 0x1d00e1fd+ it "computes the retarget of bitcoin testnet4 block 157248" $+ -- first block of the period: bits 0x19037e43, time 1791203299. Last+ -- block: time 1792638187. 1434888 s of 1209600 s is a factor of 1.18625,+ -- and 0x037e43 * 1.18625 = 0x0424d1.+ calcNextWork btcTest4 (btcTest4.genesisHeader :: BlockHeader) {timestamp = 1792638187, bits = 0x19037e43} 1791203299+ `shouldBe` 0x190424d1+ it "counts 2^255 hashes of work for a target of one" $+ -- 2^256 / (1 + 1)+ headerWork (btc.genesisHeader :: BlockHeader) {bits = 0x01010000}+ `shouldBe` 57896044618658097711785492504343953926634992332820282019728792003956564819968++activationSpec :: Spec+activationSpec = describe "first block of each Bitcoin Cash difficulty rule" $ do+ -- Expected bits come from a Python port of GetNextEDAWorkRequired and+ -- GetNextCashWorkRequired of Bitcoin Cash Node.+ it "uses the DAA for the block at the DAA height, and not for the block before" $ do+ -- DAA from block 150. Blocks every 300 seconds at bits 0x1f00ffff.+ -- Block 149 keeps the bits of its parent. Block 150 gets the DAA: 144+ -- blocks of work in 43200 seconds, twice the rate, so half the target.+ -- The DAA needs a parent at or above the retarget interval, as in+ -- Bitcoin Cash Node, so the interval is 144 blocks here.+ let net = (cashNet :: Network) {daaHeight = Just 150, targetTimespan = 144 * 600}+ required n = withChain net (storeChain (evenlySpaced net 300 n 0x1f00ffff) >> requiredAfter net (net.genesisHeader.timestamp + 300 * (fromIntegral n + 1)))+ required 148 `shouldBe` 0x1f00ffff+ required 149 `shouldBe` 0x1e7fff80+ it "uses the EDA for the block at the EDA height, and not for the block before" $ do+ -- EDA from block 20. Blocks every three hours at bits 0x1f00ffff, so the+ -- median time past moves 18 hours in six blocks, more than 12. Block 19+ -- keeps the bits of its parent. Block 20 gets a target a quarter higher:+ -- 0x00ffff + 0x003fff = 0x013ffe.+ let net = (cashNet :: Network) {edaHeight = Just 20}+ required n = withChain net (storeChain (evenlySpaced net 10800 n 0x1f00ffff) >> requiredAfter net (net.genesisHeader.timestamp + 10800 * (fromIntegral n + 1)))+ required 18 `shouldBe` 0x1f00ffff+ required 19 `shouldBe` 0x1f013ffe++minimumWorkSpec :: Spec+minimumWorkSpec = describe "minimum chain work" $ do+ -- A regtest header at bits 0x207fffff has a target of 0x7fffff * 2^232,+ -- so its work is 2^256 / (target + 1) = 2. Genesis has 2, block 1 has 4,+ -- block 2 has 6, block 3 has 8 and so on.+ let base = btcRegTest+ t0 = base.genesisHeader.timestamp+ b1 = mineOn base base.genesisHeader (t0 + 600) 0x207fffff+ b2 = mineOn base b1 (t0 + 1200) 0x207fffff+ b3 = mineOn base b2 (t0 + 1800) 0x207fffff+ b4 = mineOn base b3 (t0 + 2400) 0x207fffff+ -- other blocks on top of genesis, block 1 and block 2+ fork0 = mineBlock base 1 (headerOn base.genesisHeader (t0 + 660) 0x207fffff)+ fork1 = mineBlock base 1 (headerOn b1 (t0 + 1260) 0x207fffff)+ fork2 = mineBlock base 1 (headerOn b2 (t0 + 1860) 0x207fffff)+ -- the chain is known to have a work of 8, which block 3 reaches+ net = (base :: Network) {minimumChainWork = 8}+ it "has the work the test assumes" $+ map headerWork [base.genesisHeader, b1] `shouldBe` [2, 2]+ it "accepts a fork below the minimum chain work while the best block is below it too" $+ withChain net (connectAll net [b1, b2] >> connectHeight net myTime fork0) `shouldBe` Right 1+ it "refuses a fork whose chain has less than the minimum chain work once the best block has it" $ do+ -- best block 3 with a work of 8. A second block 1 would have 4, a second block 2 would have 6.+ withChain net (connectAll net [b1, b2, b3] >> connectHeight net myTime fork0)+ `shouldBe` Left "Header chain has less work than the minimum chain work: 1"+ withChain net (connectAll net [b1, b2, b3] >> connectHeight net myTime fork1)+ `shouldBe` Left "Header chain has less work than the minimum chain work: 2"+ it "accepts a fork whose chain has exactly the minimum chain work" $+ -- a second block 3 has a work of 8+ withChain net (connectAll net [b1, b2, b3] >> connectHeight net myTime fork2) `shouldBe` Right 3+ it "accepts the next block of the best chain" $+ withChain net (connectAll net [b1, b2, b3] >> connectHeight net myTime b4) `shouldBe` Right 4+ it "applies no minimum on regtest, where it is zero" $+ withChain base (connectAll base [b1, b2, b3] >> connectHeight base myTime fork0) `shouldBe` Right 1+ it "has the minimum chain work of Bitcoin Core 32.1 and Bitcoin Cash Node on each network" $ do+ -- nMinimumChainWork in kernel/chainparams.cpp of Bitcoin Core v32.1+ btc.minimumChainWork `shouldBe` 0x0000000000000000000000000000000000000000145ec036acc5ba740052af1a0+ btcTest.minimumChainWork `shouldBe` 0x0000000000000000000000000000000000000000000017f49f702147f10c0eb6+ btcTest4.minimumChainWork `shouldBe` 0x000000000000000000000000000000000000000000000e346a558455ade8eca9+ btcRegTest.minimumChainWork `shouldBe` 0+ -- chainparamsconstants.h of Bitcoin Cash Node at 3d13ce8+ bch.minimumChainWork `shouldBe` 0x0000000000000000000000000000000000000000030fd678912166474fe93f14+ bchTest.minimumChainWork `shouldBe` 0x00000000000000000000000000000000000000000000011fdbbe582349040762+ bchTest4.minimumChainWork `shouldBe` 0x000000000000000000000000000000000000000000000000bd5a1b5bda45b5da+ bchRegTest.minimumChainWork `shouldBe` 0++coreProofSpec :: Spec+coreProofSpec = describe "merkle proofs made by Bitcoin Core 32.1" $ do+ -- Output of gettxoutproof on a regtest block of seven transactions,+ -- 307105bce8534c8829f50dbee4a0f5d5f702327443d7078853d0003b19099718. The+ -- result of that call is a serialized merkle block.+ let proofOfOne = fromJust $ decodeHex "00000020890b601aa1dcb84e3647ac08bba63d1b2f4c20f493fa83c81c96e113775e525996ec6f2e21c4e47055a7977fefa3567adcf9618591645e1aa004a366a7bd60d5ace5ec6affff7f2000000000070000000431094d2f5d072ffc7cee499023fb27b7910b7c0579f43d6ef46ae4a5ee78d21bacda1c3873e5bc3343400027c94c3ee281134ada869cbc15533593ededf6bd85ccac70b301ce3c55ba6c315c05dd255396d7c76add7ce5fd0780c741353d02c1d444e4de84cf6931a28901c210ad7d81a63e8e9c66ab239e00a0dceb06cb2fa3012b"+ proofOfTwo = fromJust $ decodeHex "00000020890b601aa1dcb84e3647ac08bba63d1b2f4c20f493fa83c81c96e113775e525996ec6f2e21c4e47055a7977fefa3567adcf9618591645e1aa004a366a7bd60d5ace5ec6affff7f2000000000070000000631094d2f5d072ffc7cee499023fb27b7910b7c0579f43d6ef46ae4a5ee78d21bacda1c3873e5bc3343400027c94c3ee281134ada869cbc15533593ededf6bd85ccac70b301ce3c55ba6c315c05dd255396d7c76add7ce5fd0780c741353d02c1406b3b9a3292f7e0efcd4d4a63197c97d4ab4a7109e8e8ece3dfbd25db25fcece9cc6977b0609eb4c3a5bb98dc6e113a87136a2c2d75320d367ddaa32a20a0d680edfad428b72d1713a241614633c4f0ec6109b7c57cd8a42e6b4c6bd0d736cc02eb02"+ tx3 = displayTxHash "c1023d3541c78007fde57cdd6ac7d7965325dd055c316cba553cce01b370accc"+ tx5 = displayTxHash "d6a0202aa3da7d360d32752d2c6a13873a116edc98bba5c3b49e60b07769cce9"+ parseMerkle bytes = runGetS deserialize bytes :: Either String MerkleBlock+ it "reads the proof of the transaction at position 3" $ do+ fmap (\m -> (headerHash m.header, m.txn)) (parseMerkle proofOfOne)+ `shouldBe` Right ("307105bce8534c8829f50dbee4a0f5d5f702327443d7078853d0003b19099718", 7)+ fmap (merkleBlockIndexedTxs btcRegTest) (parseMerkle proofOfOne) `shouldBe` Right (Right [(3, tx3)])+ it "reads the proof of the transactions at positions 3 and 5" $+ fmap (merkleBlockIndexedTxs btcRegTest) (parseMerkle proofOfTwo) `shouldBe` Right (Right [(3, tx3), (5, tx5)])+ it "writes both proofs back to the same bytes" $ do+ fmap (runPutS . serialize) (parseMerkle proofOfOne) `shouldBe` Right proofOfOne+ fmap (runPutS . serialize) (parseMerkle proofOfTwo) `shouldBe` Right proofOfTwo
test/Haskoin/Crypto/Keys/ExtendedSpec.hs view
@@ -17,6 +17,7 @@ import Data.Bytes.Serial import Data.Default (def) import Data.Either (isLeft)+import Data.List (isInfixOf) import Data.Maybe (fromJust, isJust, isNothing) import Data.Ord (comparing) import Data.String (fromString)@@ -30,6 +31,7 @@ import Haskoin.Script import Haskoin.Util import Haskoin.Util.Arbitrary+import System.Timeout (timeout) import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck@@ -112,6 +114,12 @@ 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 a string of a million characters at once, without decoding it" $ do+ -- Decoding that much Base58 takes minutes. The length check comes first.+ timeout 1000000 (evaluate (xPrvImport btc (T.replicate 1000000 "z")))+ `shouldReturn` Just Nothing+ timeout 1000000 (evaluate (xPubImport btc ctx (T.replicate 1000000 "z")))+ `shouldReturn` Just Nothing it "refuses an extended key of another network" $ do xPrvImport btcTest xprv1Text `shouldBe` Nothing xPrvImport btc tprv1Text `shouldBe` Nothing@@ -120,6 +128,17 @@ 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 "writes a key with the version bytes of each of the eight networks" $ do+ -- xprv and xpub (0488ade4, 0488b21e) on the two main networks, tprv and+ -- tpub (04358394, 043587cf) on the six others: chainparams.cpp of+ -- Bitcoin Core and of Bitcoin Cash Node. The key is the master key of+ -- BIP32 test vector 1.+ forM_ [btc, bch] $ \net -> do+ xPrvExport net (xprv1 ctx) `shouldBe` xprv1Text+ xPubExport net ctx (xpub1 ctx) `shouldBe` xpub1Text+ forM_ [btcTest, btcTest4, btcRegTest, bchTest, bchTest4, bchRegTest] $ \net -> do+ xPrvExport net (xprv1 ctx) `shouldBe` tprv1Text+ xPubExport net ctx (xpub1 ctx) `shouldBe` tpub1Text 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@@ -127,12 +146,61 @@ -- BIP32 test vector 5: private key 0 (unmarshalJSON btc "\"xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx\"" :: Maybe XPrvKey) `shouldBe` Nothing- describe "Master key" $+ describe "Binary extended keys" $ do+ -- The 78 bytes of the master keys of BIP32 test vector 1: version, depth+ -- 0, parent 00000000, index 0, the chain code, then the key+ let header = "000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"+ xpubBytes = hex ("0488b21e" <> header <> "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2")+ xprvBytes = hex ("0488ade4" <> header <> "00e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")+ it "reads the 78 bytes of an extended public key" $+ fmap (xPubExport btc ctx) (unmarshal (btc, ctx) xpubBytes) `shouldBe` Right xpub1Text+ it "reads the 78 bytes of an extended private key" $+ fmap (xPrvExport btc) (unmarshal btc xprvBytes) `shouldBe` Right xprv1Text+ it "refuses an extended public key whose key is in the 65-byte form" $+ -- the same key with its y coordinate, 110 bytes in all+ ( unmarshal+ (btc, ctx)+ ( hex+ ( "0488b21e"+ <> header+ <> "0439a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2\+ \3cbe7ded0e7ce6a594896b8f62888fdbc5c8821305e2ea42bf01e37300116281"+ )+ ) ::+ Either String XPubKey+ )+ `shouldSatisfy` either ("compressed public key" `isInfixOf`) (const False)+ it "refuses bytes after the 78 bytes of an extended key" $ do+ (unmarshal (btc, ctx) (xpubBytes <> "\x00") :: Either String XPubKey)+ `shouldSatisfy` either ("bytes left over" `isInfixOf`) (const False)+ (unmarshal btc (xprvBytes <> "\x00") :: Either String XPrvKey)+ `shouldSatisfy` either ("bytes left over" `isInfixOf`) (const False)+ describe "Master key" $ do 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+ -- BIP32: the seed is "between 128 and 512 bits", that is 16 to 64 bytes+ let badSeed = DerivationException "Invalid seed: its length must be between 16 and 64 bytes"+ it "is made from the 16-byte seed of BIP32 test vector 1" $+ xPrvExport btc (makeXPrvKey (hex "000102030405060708090a0b0c0d0e0f")) `shouldBe` xprv1Text+ it "is made from the 64-byte seed of BIP32 test vector 2" $+ xPrvExport+ btc+ ( makeXPrvKey+ ( hex+ "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2\+ \9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"+ )+ )+ `shouldBe` "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"+ it "is not made from a seed of 15 bytes" $+ evaluate (makeXPrvKey (B.replicate 15 1)) `shouldThrow` (== badSeed)+ it "is not made from a seed of 65 bytes" $+ evaluate (makeXPrvKey (B.replicate 65 1)) `shouldThrow` (== badSeed)+ it "is not made from an empty seed" $+ evaluate (makeXPrvKey B.empty) `shouldThrow` (== badSeed) 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@@ -159,6 +227,49 @@ evaluate (prvSubKey ctx m 0x80000000) `shouldThrow` (== hardIndex) evaluate (hardSubKey ctx m 0x80000000) `shouldThrow` (== hardIndex) evaluate (pubSubKey ctx (deriveXPubKey ctx m) 0x80000000) `shouldThrow` (== hardIndex)+ -- The depth is one byte, so a master key has descendants down to depth+ -- 255 and no further+ let steps n = concat (replicate n "/0")+ depthOf (XPrv k _) = k.depth+ depthOf (XPub k _) = k.depth+ it "applyPath derives a path of 255 steps from a master key" $ do+ fmap depthOf (applyPath ctx (fromString ("m" <> steps 255)) (XPrv m btc)) `shouldBe` Right 255+ fmap depthOf (applyPath ctx (fromString ("M" <> steps 255)) (XPub (deriveXPubKey ctx m) btc)) `shouldBe` Right 255+ it "applyPath gives an error value for a path of 256 steps from a master key" $ do+ applyPath ctx (fromString ("m" <> steps 256)) (XPrv m btc)+ `shouldBe` Left "applyPath: Maximum derivation depth exceeded"+ applyPath ctx (fromString ("M" <> steps 256)) (XPrv m btc)+ `shouldBe` Left "applyPath: Maximum derivation depth exceeded"+ applyPath ctx (fromString ("M" <> steps 256)) (XPub (deriveXPubKey ctx m) btc)+ `shouldBe` Left "applyPath: Maximum derivation depth exceeded"+ it "applyPath gives an error value for one step from a key at depth 255" $+ applyPath ctx "m/0" (XPrv atDepth255 btc)+ `shouldBe` Left "applyPath: Maximum derivation depth exceeded"+ it "tryDerivePath derives what derivePath derives" $+ -- BIP32 test vector 1, chain m/0'/1+ fmap (xPrvExport btc) (tryDerivePath ctx (Deriv :| 0 :/ 1 :: DerivPath) m)+ `shouldBe` Right "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"+ it "tryDerivePath gives an error value for a path of 256 steps from a master key" $+ tryDerivePath ctx (listToPath (replicate 256 0)) m+ `shouldBe` Left "Maximum derivation depth exceeded"+ it "tryDerivePubPath derives what derivePubPath derives" $+ -- BIP32 test vector 2, chain m/0, from the master public key+ fmap (xPubExport btc ctx) (tryDerivePubPath ctx (Deriv :/ 0) (xpub2 ctx))+ `shouldBe` Right "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"+ it "tryDerivePubPath gives an error value for a path of 256 steps from a master key" $+ tryDerivePubPath ctx (foldl (:/) Deriv (replicate 256 0)) (deriveXPubKey ctx m)+ `shouldBe` Left "Maximum derivation depth exceeded"+ it "derivePath throws a DerivationException for a path of 256 steps from a master key" $+ evaluate (derivePath ctx (listToPath (replicate 256 0)) m) `shouldThrow` (== tooDeep)+ it "the path constructors refuse an index of 2^31 or more" $ do+ evaluate (Deriv :/ 0x80000000 :: DerivPath) `shouldThrow` (== hardIndex)+ evaluate (Deriv :| 0x80000000 :: DerivPath) `shouldThrow` (== hardIndex)+ evaluate (Deriv :| 0xffffffff :: HardPath) `shouldThrow` (== hardIndex)+ evaluate (Deriv :/ 0xffffffff :: SoftPath) `shouldThrow` (== hardIndex)+ it "the path constructors take the largest index, 2^31 - 1" $ do+ pathToStr (Deriv :/ 0x7fffffff :: DerivPath) `shouldBe` "/2147483647"+ pathToStr (Deriv :| 0x7fffffff :: DerivPath) `shouldBe` "/2147483647'"+ pathToList (Deriv :| 0x7fffffff :: DerivPath) `shouldBe` [0xffffffff] describe "Child index" $ do let m = makeXPrvKey (fromJust (decodeHex m1)) it "a hardened child stores its index with the hard bit set" $ do@@ -221,16 +332,47 @@ 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+ let Right (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]+ (map lst3 . take 2 <$> deriveMSAddrs ctx [xpub1 ctx, xpub2 ctx] 2 0) `shouldBe` Right [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)]+ (map addressAndIndex . take 1 <$> deriveMSAddrs ctx [xpub1 ctx, xpub2 ctx] 2 1)+ `shouldBe` Right [(Just "3Pp8RkaJc8eNreFuTTTJ92QfXECQMXDEoE", 1)]+ it "deriveMSAddr gives the P2SH address of 15 keys, the most that fit" $+ -- 1-of-15 of child 0 of m/0 to m/14 of vector 1. The redeem script is+ -- 3 + 15 × 34 = 513 bytes. The address is the one that createmultisig+ -- of Bitcoin Core 32.1 gives for these keys on regtest.+ (addrToText btcRegTest . fst <$> deriveMSAddr ctx (accountKeys ctx 15) 1 0)+ `shouldBe` Right (Just "2NFJxQqeqnzhLx9a1UBhpdgYVGxcd95Ka1b")+ it "deriveMSAddr has no address for 16 keys, whose redeem script cannot be spent" $+ -- 3 + 16 × 34 = 547 bytes, and a push holds at most 520. Bitcoin Core+ -- answers "redeemScript exceeds size limit: 547 > 520".+ (fst <$> deriveMSAddr ctx (accountKeys ctx 16) 1 0)+ `shouldBe` Left "The redeem script is 547 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ it "deriveMSAddr has no address for 20 keys" $+ -- 3 + 20 × 34 + 1 = 684 bytes+ (fst <$> deriveMSAddr ctx (accountKeys ctx 20) 1 0)+ `shouldBe` Left "The redeem script is 684 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ it "deriveMSAddrs and the path forms fail for 16 keys as deriveMSAddr does" $ do+ let tooLong = Left "The redeem script is 547 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ (length . take 1 <$> deriveMSAddrs ctx (accountKeys ctx 16) 1 0) `shouldBe` tooLong+ (fst <$> derivePathMSAddr ctx (accountKeys ctx 16) (Deriv :/ 1) 1 0) `shouldBe` tooLong+ (length . take 1 <$> derivePathMSAddrs ctx (accountKeys ctx 16) (Deriv :/ 1) 1 0) `shouldBe` tooLong+ it "deriveMSAddr gives an error value for a signature count the keys cannot meet" $ do+ (fst <$> deriveMSAddr ctx [xpub1 ctx, xpub2 ctx] 3 0)+ `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys"+ (fst <$> deriveMSAddr ctx [xpub1 ctx, xpub2 ctx] 0 0)+ `shouldBe` Left "encodeOutput: multisig must require at least one signature"+ (fst <$> deriveMSAddr ctx [xpub1 ctx, xpub2 ctx] (-1) 0)+ `shouldBe` Left "encodeOutput: multisig must require at least one signature"+ (fst <$> deriveMSAddr ctx [] 1 0)+ `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys"+ (length . take 1 <$> deriveMSAddrs ctx [xpub1 ctx, xpub2 ctx] 3 0)+ `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys" 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@@ -243,11 +385,11 @@ `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+ let Right (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)]+ (map addressAndIndex . take 1 <$> derivePathMSAddrs ctx [xpub1 ctx, xpub2 ctx] (Deriv :/ 1) 2 0)+ `shouldBe` Right [(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)@@ -288,6 +430,8 @@ forAll arbitraryDerivPath $ \b -> compare a b == comparing (\p -> (length (pathToList p), pathToList p)) a b describe "Path parsing" $ do+ -- BIP32 writes a hardened index with a subscript H, as in m/0H/1. Bitcoin+ -- Core and BIP380 write an apostrophe or a small h. 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)@@ -337,9 +481,16 @@ 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- it "dublic derivations" $ testDerivePubPath ctx+ it "applyPath derives private and public keys along a path" $ testApplyPath ctx+ it "applyPath refuses private derivations on a public key and hard public derivations" $ testBadApplyPath ctx+ it "applyPath gives the keys of BIP32 test vector 1, chain m/0'/1" $ do+ let exported (XPrv k n) = xPrvExport n k+ exported (XPub k n) = xPubExport n ctx k+ fmap exported (applyPath ctx "m/0'/1" (XPrv (xprv1 ctx) btc))+ `shouldBe` Right "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"+ fmap exported (applyPath ctx "M/0'/1" (XPrv (xprv1 ctx) btc))+ `shouldBe` Right "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"+ it "public derivations" $ testDerivePubPath ctx it "private derivations" $ testDerivePrvPath ctx it "path parsing" testParsePath it "from json" testFromJsonPath@@ -395,6 +546,15 @@ xpub1, xpub2 :: Ctx -> XPubKey xpub1 ctx = fromJust (xPubImport btc ctx xpub1Text) xpub2 ctx = fromJust (xPubImport btc ctx xpub2Text)++-- | The extended public keys m/0 to m/(n-1) of BIP 32 test vector 1.+accountKeys :: Ctx -> Int -> [XPubKey]+accountKeys ctx n =+ [ deriveXPubKey ctx (prvSubKey ctx master (fromIntegral i))+ | i <- [0 .. n - 1]+ ]+ where+ master = makeXPrvKey (hex "000102030405060708090a0b0c0d0e0f") xprv1 :: Ctx -> XPrvKey xprv1 _ = fromJust (xPrvImport btc xprv1Text)
test/Haskoin/Crypto/Keys/MnemonicSpec.hs view
@@ -3,7 +3,10 @@ module Haskoin.Crypto.Keys.MnemonicSpec (spec) where +import Control.DeepSeq (NFData, force)+import Control.Exception (evaluate) import Control.Monad (forM_, zipWithM_)+import Crypto.Hash (SHA256 (..), hashWith) import Data.Bits (shiftR, (.&.)) import Data.ByteString qualified as BS import Data.Either (fromRight)@@ -13,12 +16,15 @@ import Data.Serialize (Serialize, encode) import Data.Text (Text) import Data.Text qualified as T+import Data.Text.Encoding qualified as E+import Data.Text.IO qualified as TIO import Data.Vector qualified as V import Data.Word (Word32, Word64) import Haskoin (btc) import Haskoin.Crypto import Haskoin.Util import Haskoin.Util.Arbitrary+import System.Timeout (timeout) import Test.HUnit import Test.Hspec import Test.QuickCheck hiding ((.&.))@@ -26,6 +32,12 @@ spec :: Spec spec = describe "mnemonic" $ do+ -- data/bip39_wordlist.txt is bip-0039/english.txt of the BIPs repository,+ -- and data/bip39_vectors.json the vectors.json of python-mnemonic, which+ -- the BIP points to; its English vectors are also written out below as+ -- trezorVectors+ wordListFile <- runIO (TIO.readFile "data/bip39_wordlist.txt")+ vectorsFile <- runIO (readTestFile "bip39_vectors.json" :: IO (M.Map Text [(Text, Mnemonic, Text, Text)])) it "entropy to mnemonic sentence" toMnemonicTest it "mnemonic sentence to entropy" fromMnemonicTest it "mnemonic sentence to seed" mnemonicToSeedTest@@ -69,9 +81,24 @@ `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" $+ it "refuses a word that is not in the list, naming its position and not the word" $+ -- the list is lower case, so "Abandon" is not in it fromMnemonic "Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"- `shouldBe` Left "fromMnemonic: words not in the list: Abandon"+ `shouldBe` Left "fromMnemonic: word 1 is not in the list"+ it "names the positions of two unknown words" $+ fromMnemonic "legal winner thankk year wave sausage wort useful legal winner thank yellow"+ `shouldBe` Left "fromMnemonic: words 3 and 7 are not in the list"+ it "keeps every word of an upper-case sentence out of the error text" $ do+ -- A sentence typed in capitals is a real secret. It used to be+ -- echoed whole.+ let capitals = "LEGAL WINNER THANK YEAR WAVE SAUSAGE WORTH USEFUL LEGAL WINNER THANK YELLOW"+ refusal = Left "fromMnemonic: words 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 and 12 are not in the list"+ fromMnemonic capitals `shouldBe` refusal+ mnemonicToSeed "" capitals `shouldBe` refusal+ it "reports a failed checksum without its values" $+ -- "zoo" in place of "yellow": every word is in the list+ fromMnemonic "legal winner thank year wave sausage worth useful legal winner thank zoo"+ `shouldBe` Left "fromMnemonic: checksum failed" 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")@@ -89,7 +116,114 @@ M.size wordListMap `shouldBe` 2048 M.lookup "abandon" wordListMap `shouldBe` Just 0 M.lookup "zoo" wordListMap `shouldBe` Just 2047+ it "has the published English word list, by its SHA-256" $+ -- sha256sum of bip-0039/english.txt in the BIPs repository, one word+ -- per line with a newline after each+ show (hashWith SHA256 (E.encodeUtf8 (T.unlines (V.toList wordList))))+ `shouldBe` "2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda"+ it "has the word list of the data directory, word for word" $+ V.toList wordList `shouldBe` T.lines wordListFile+ it "has the 24 English vectors of the published file, as written out here" $ do+ M.lookup "english" vectorsFile `shouldBe` Just trezorVectors+ length trezorVectors `shouldBe` 24+ it "accepts the 24-word sentence of Trezor's firmware tests" $+ -- 22 times "abstract", then "artefact electric"; the entropy is the+ -- 11-bit index of each word in a row, 6 for abstract, 36 and 572+ fmap encodeHex (fromMnemonic (T.unwords (replicate 22 "abstract" ++ ["artefact", "electric"])))+ `shouldBe` Right "00e01c0380700e01c0380700e01c0380700e01c0380700e01c0380700e01c33a"+ it "refuses each old vector sentence with a word put in front, as Trezor's firmware does" $+ -- 13, 19 or 25 words+ forM_ mss $ \m ->+ fromMnemonic ("above " <> m) `shouldBe` Left ("fromMnemonic: wrong number of words: " <> show (length (T.words m) + 1))+ it "refuses each old vector sentence without its first word, as Trezor's firmware does" $+ -- 11, 17 or 23 words+ forM_ mss $ \m ->+ fromMnemonic (T.unwords (drop 1 (T.words m))) `shouldBe` Left ("fromMnemonic: wrong number of words: " <> show (length (T.words m) - 1))+ it "refuses the wrong-checksum sentence of python-mnemonic's tests" $+ fromMnemonic "bless cloud wheel regular tiny venue bird web grief security dignity zoo"+ `shouldBe` Left "fromMnemonic: checksum failed"+ it "refuses the sentences of the JavaScript library's custom word list, naming their positions" $+ -- bitcoinjs/bip39 tests them against a word list of its own+ fromMnemonic "aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n ab0ut"+ `shouldBe` Left "fromMnemonic: words 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 and 12 are not in the list"+ it "has words of 3 to 8 letters, so a longer word is never looked up" $+ -- the lengths of bip-0039/english.txt+ (minimum (fmap T.length wordList), maximum (fmap T.length wordList)) `shouldBe` (3, 8)+ it "refuses 36 bytes of entropy, the first size above the limit" $+ toMnemonic (BS.replicate 36 0)+ `shouldBe` Left "toMnemonic: maximum entropy is 32 bytes (256 bits)"+ it "refuses 12 bytes of entropy, the last size below the limit" $+ toMnemonic (BS.replicate 12 0)+ `shouldBe` Left "toMnemonic: minimum entropy is 16 bytes (128 bits)"+ it "refuses a sentence of 9 words and one of 27, the sizes next to the allowed ones" $ do+ fromMnemonic (T.unwords (replicate 9 "abandon"))+ `shouldBe` Left "fromMnemonic: wrong number of words: 9"+ fromMnemonic (T.unwords (replicate 27 "abandon"))+ `shouldBe` Left "fromMnemonic: wrong number of words: 27"+ it "gives no seed for a sentence with a wrong checksum" $+ -- twelve times "abandon": the checksum of 16 zero bytes is "about"+ mnemonicToSeed "" (T.unwords (replicate 12 "abandon"))+ `shouldBe` Left "fromMnemonic: checksum failed"+ it "gives no seed for a sentence of 11 words" $+ mnemonicToSeed "" (T.unwords (replicate 11 "abandon"))+ `shouldBe` Left "fromMnemonic: wrong number of words: 11"+ it "gives the same seed whatever the white space between the words" $+ -- the second Trezor vector, with two spaces, a tab and a line break+ fmap encodeHex (mnemonicToSeed "TREZOR" " legal winner thank\tyear wave sausage worth useful\nlegal winner thank yellow ")+ `shouldBe` Right "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607"+ it "takes an ASCII passphrase as it is" $+ -- lower-case letters and spaces: the seed python-mnemonic 0.21 gives+ fmap encodeHex (mnemonicToSeed "correct horse battery staple" abandon12)+ `shouldBe` Right "af1c9b2fb5ae8365dc09b9abfd57c2319bf328aee47fad36e1b06c5c115f20285dd3b29aa5a84bbc8167237869e80a0556581cecfab3f2363f844f082bc87837"+ it "refuses a sentence of one word of 10,001 characters within a second" $+ -- "a" followed by 10,000 combining acute accents. Normalising it took+ -- about ten seconds, and the time grew with the square of the length.+ withinOneSecond (fromMnemonic ("a" <> T.replicate 10000 "\x0301"))+ `shouldReturn` Just (Left "fromMnemonic: wrong number of words: 1")+ it "refuses a word of 10,001 characters in a sentence of 12 words within a second" $+ withinOneSecond (fromMnemonic (T.unwords (("a" <> T.replicate 10000 "\x0301") : replicate 11 "abandon")))+ `shouldReturn` Just (Left "fromMnemonic: word 1 is not in the list")+ it "refuses a passphrase with 10,000 combining characters in a row within a second" $+ withinOneSecond (mnemonicToSeed ("a" <> T.replicate 10000 "\x0301") abandon12)+ `shouldReturn` Just (Left "mnemonicToSeed: the passphrase has more than 30 combining characters in a row")+ it "accepts a passphrase with 30 combining characters in a row, and refuses 31" $ do+ -- The limit of the Stream-Safe Text Format of Unicode (UAX #15). The+ -- seed is the one python-mnemonic 0.21 gives.+ fmap encodeHex (mnemonicToSeed ("a" <> T.replicate 30 "\x0301") abandon12)+ `shouldBe` Right "dcd74c9cf73d2153256e7eb6634c8e3210d81067c00eb522dae1e5b69b6e9068fc422798c5933be591f8828f1f516bbfe4add8f46c8ac854ff17315ee90df46d"+ mnemonicToSeed ("a" <> T.replicate 31 "\x0301") abandon12+ `shouldBe` Left "mnemonicToSeed: the passphrase has more than 30 combining characters in a row"+ it "counts the combining characters of the normalised passphrase, not of its spelling" $ do+ -- U+FF9E, the halfwidth voiced sound mark, is a letter that NFKD turns+ -- into the combining character U+3099. The seed is python-mnemonic's.+ fmap encodeHex (mnemonicToSeed ("\xff76" <> T.replicate 30 "\xff9e") abandon12)+ `shouldBe` Right "ff2b0cc184bad26986cd04e499def27a5c50a2d35b95489d66ee2a04de568e639d70b3e3ea553956d55fe65629c030f446d63407472d6982270221a26417b88b"+ mnemonicToSeed ("\xff76" <> T.replicate 31 "\xff9e") abandon12+ `shouldBe` Left "mnemonicToSeed: the passphrase has more than 30 combining characters in a row"+ it "accepts a long passphrase of accented letters, in either spelling" $ do+ -- 1,000 times "é", decomposed and precomposed: the seed of+ -- python-mnemonic 0.21 for both+ let seed = "62654f25fab756984c5220d458717da768c5e31db01773f14a6e4fbde575a03d0d6edca6fa48ad7c71b4ebb9822fbb25f0ef95d3246261016e2aee21361b247e"+ fmap encodeHex (mnemonicToSeed (T.replicate 1000 "e\x0301") abandon12) `shouldBe` Right seed+ fmap encodeHex (mnemonicToSeed (T.replicate 1000 "\x00e9") abandon12) `shouldBe` Right seed+ it "gives 15 and 21 words for 20 and 28 bytes of entropy, with their seeds and keys" $+ -- The published vectors have no 15- or 21-word case. These were+ -- computed with an independent Python implementation of BIP39 from+ -- hashlib: the checksum is the first 5 or 7 bits of the SHA-256 of+ -- the entropy, the seed is PBKDF2-HMAC-SHA512 of the sentence with+ -- 2048 rounds and the salt "mnemonic" ++ "TREZOR", and the key is+ -- the BIP32 master key of the seed.+ forM_ fifteenAndTwentyOneWordVectors $ \(entTxt, mnem, seedTxt, xprvTxt) -> do+ let ent = fromJust (decodeHex entTxt)+ toMnemonic ent `shouldBe` Right mnem+ fmap encodeHex (mnemonicToSeed "TREZOR" mnem) `shouldBe` Right seedTxt+ fmap (xPrvExport btc . makeXPrvKey) (mnemonicToSeed "TREZOR" mnem) `shouldBe` Right xprvTxt +-- | The value in normal form, or nothing when computing it takes more than+-- a second.+withinOneSecond :: (NFData a) => a -> IO (Maybe a)+withinOneSecond = timeout 1000000 . evaluate . force+ -- | The first Trezor sentence, for 16 zero bytes of entropy. abandon12 :: Mnemonic abandon12 =@@ -135,7 +269,7 @@ f = assertBool "" . h h m = case fromMnemonic m of Right _ -> False- Left err -> "fromMnemonic: checksum failed:" `isPrefixOf` err+ Left err -> err == "fromMnemonic: checksum failed" emptyMnemonicTest :: Assertion emptyMnemonicTest =@@ -154,6 +288,9 @@ assertEqual "mnemonicToSeed" (Right seed) $ mnemonicToSeed "TREZOR" mnem assertEqual "xPrv" xprv $ makeXPrvKey seed +-- The next three lists are the English vectors of an earlier vectors.json+-- of python-mnemonic, whose random cases (from the 13th on) were replaced+-- later; Trezor's firmware still tests the sentences as valid. ents :: [Text] ents = [ "00000000000000000000000000000000",@@ -346,6 +483,32 @@ "beyond stage sleep clip because twist token leaf atom beauty genius food\ \ business side grid unable middle armed observe pair crouch tonight away\ \ zoo"+ ]++-- | Entropy, sentence, seed with the passphrase "TREZOR" and master key for+-- 20 and 28 bytes of entropy, which the published vectors do not cover.+fifteenAndTwentyOneWordVectors :: [(Text, Mnemonic, Text, Text)]+fifteenAndTwentyOneWordVectors =+ [ ( "0000000000000000000000000000000000000000",+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon address",+ "fa08713f46bf5cb48728ceb70e3aae1bc53c5cb7b4e29c5610261d1cbb7be3bed4d805256fec515754d2be35974fc5da678168e9d9bb0cb70948026923b0def3",+ "xprv9s21ZrQH143K2nE8x21M7hF4msPN7WC7b6yCn1usVKBA9myCGie4yQCqsw479qmbjXeeRZgMjnNwvWweSCbBeUSPADo2aZbfpkTvJ8de1Qi"+ ),+ ( "ffffffffffffffffffffffffffffffffffffffff",+ "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrist",+ "bfee6f9d2bcfa1331bd6482a24abca521e5f7e769498b9a0146672194c7356e4e409be22bc379c8b64fee2aa24b54d3ec20d10a083eaa5d1d6b4b365941ad37c",+ "xprv9s21ZrQH143K4YQcGfMEpbEoWtj4dJ8aSkUPCXEbg3kWsR8kcdcmhGK3y4swwdjUpyGBfWT9bpizzQJrznSRuFx9K4FKeoNEYT45rLY11Hc"+ ),+ ( "00000000000000000000000000000000000000000000000000000000",+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon admit",+ "e7dadc189d2e8d07ac278d9ec98a1d2d327e4a6b7df494c00cbf2cbf2d3543dac7000fc72d4ada8d9997dc8db388ff22c6d79f604a7455f2df5534a28eee04c6",+ "xprv9s21ZrQH143K3CXWs34yKD87zsm1AVExnEEUnH8rKWqWVguXRprSz8bFFZDbKY7oai19nPTZ68HnW8xX62HFr8WxsNL4CEYmGLcTxAyKNHd"+ ),+ ( "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",+ "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo veteran",+ "4aa0af4ca02ef1d9fa675cd02aa06d318425564e7fadd3d51b6165cc56d77398f28d8522073cd036c2a4a24a83e919211c84500d96cb120084e613ff5fcd96c1",+ "xprv9s21ZrQH143K4DvgrFvRv4J7m2qehW4MCdmLfRrpAJQFzgVFGi8L9QaapqJPQLyX9SAeGWATu6vXNYmo542LR8yMf4W7DMF1Xgsp96RYWDy"+ ) ] -- Vectors from https://github.com/trezor/python-mnemonic/blob/master/vectors.json
+ test/Haskoin/Crypto/Keys/Slip39Spec.hs view
@@ -0,0 +1,762 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-}++-- The vectors of the reference implementation live in+-- data/slip39_vectors.json, copied from+-- https://github.com/trezor/python-shamir-mnemonic/blob/master/vectors.json:+-- a description, the mnemonics, the master secret they give with the+-- passphrase "TREZOR" (empty when they must be refused) and the BIP-32 key+-- of that secret. The split cases in data/slip39_split.json were made by+-- test/Haskoin/Crypto/Keys/slip39_verify.py, an implementation of the+-- specification independent of this one: a scheme, the parameters and the+-- random values of a split, and the mnemonics they must give. The word list+-- in data/slip39_wordlist.txt is the file of the specification.+module Haskoin.Crypto.Keys.Slip39Spec (spec) where++import Control.DeepSeq (force)+import Control.Exception (evaluate)+import Control.Monad (forM_)+import Control.Monad.State.Strict (State, modify, runState)+import Crypto.Hash (SHA256 (..), hashWith)+import Crypto.Random (drgNewTest, getRandomBytes, withDRG)+import Data.Aeson (FromJSON (..), withObject, (.:))+import Data.ByteString (ByteString)+import Data.ByteString qualified as B+import Data.Char (ord)+import Data.List (nub, sort, subsequences)+import Data.Map.Strict qualified as M+import Data.Maybe (fromJust)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding qualified as E+import Data.Text.IO qualified as TIO+import Data.Vector qualified as V+import Haskoin (btc, makeXPrvKey, xPrvExport)+import Haskoin.Crypto.Keys.Slip39+import Haskoin.Util (decodeHex, encodeHex, readTestFile)+import System.Timeout (timeout)+import Test.Hspec+import Test.QuickCheck++-- | A case of data/slip39_split.json.+data SplitCase = SplitCase+ { caseDescription :: Text,+ caseScheme :: Scheme,+ caseIdentifier :: Int,+ caseExtendable :: Bool,+ caseIterationExponent :: Int,+ casePassphrase :: Text,+ caseMasterSecret :: ByteString,+ caseRandom :: [SplitRandom],+ caseMnemonics :: [[Text]]+ }++instance FromJSON SplitCase where+ parseJSON = withObject "SplitCase" $ \o ->+ SplitCase+ <$> o .: "description"+ <*> (o .: "scheme" >>= withObject "Scheme" (\s -> Scheme <$> s .: "groupThreshold" <*> s .: "groups"))+ <*> o .: "identifier"+ <*> o .: "extendable"+ <*> o .: "iterationExponent"+ <*> o .: "passphrase"+ <*> (hex <$> o .: "masterSecret")+ <*> (o .: "random" >>= mapM (withObject "SplitRandom" (\r -> SplitRandom <$> (map hex <$> r .: "shares") <*> (hex <$> r .: "digest"))))+ <*> o .: "mnemonics"++hex :: Text -> ByteString+hex = fromJust . decodeHex++spec :: Spec+spec = do+ vectors <- runIO (readTestFile "slip39_vectors.json" :: IO [(Text, [Text], Text, Text)])+ splitCases <- runIO (readTestFile "slip39_split.json" :: IO [SplitCase])+ wordListFile <- runIO (TIO.readFile "data/slip39_wordlist.txt")+ describe "the word list" $ do+ it "has the 1024 words of the specification, academic to zero" $ do+ V.length slip39WordList `shouldBe` 1024+ V.head slip39WordList `shouldBe` "academic"+ V.last slip39WordList `shouldBe` "zero"+ it "is the published file, by its SHA-256" $+ -- sha256sum of slip-0039/wordlist.txt in the slips repository, one+ -- word per line with a newline after each+ show (hashWith SHA256 (E.encodeUtf8 (T.unlines (V.toList slip39WordList))))+ `shouldBe` "bcc4555340332d169718aed8bf31dd9d5248cb7da6e5d355140ef4f1e601eec3"+ it "is the file in the data directory, word for word" $+ V.toList slip39WordList `shouldBe` T.lines wordListFile+ it "is sorted" $+ V.toList slip39WordList `shouldBe` sort (V.toList slip39WordList)+ it "has words of 4 to 8 letters" $+ forM_ slip39WordList $ \w -> T.length w `shouldSatisfy` (\n -> n >= 4 && n <= 8)+ it "has distinct first four letters" $+ length (nub (map (T.take 4) (V.toList slip39WordList))) `shouldBe` 1024+ it "gives each word its index" $ do+ M.lookup "academic" slip39WordListMap `shouldBe` Just 0+ M.lookup "satoshi" slip39WordListMap `shouldBe` Just 781+ M.lookup "zero" slip39WordListMap `shouldBe` Just 1023+ M.size slip39WordListMap `shouldBe` 1024+ it "reads the words of a mnemonic in any case" $+ mnemonicToIndices "Academic ZERO satoshi" `shouldBe` Right [0, 1023, 781]+ it "names the position of a word that is not in the list, and not the word" $+ -- a share is a secret, and a mistyped word is most of the right one+ mnemonicToIndices "academic abandon" `shouldBe` Left "Word 2 is not in the SLIP-39 word list"+ it "reads ASCII letters only: a look-alike of another script is not a letter of the list" $+ -- U+212A KELVIN SIGN lower-cases to "k" under the Unicode rules+ mnemonicToIndices "\x212A\&ernel kidney" `shouldBe` Left "Word 1 is not in the SLIP-39 word list"+ it "writes indices as words" $+ indicesToMnemonic [0, 781, 1023] `shouldBe` Right "academic satoshi zero"+ it "refuses an index outside the list instead of raising an error" $ do+ indicesToMnemonic [0, 1024] `shouldBe` Left "The word index must be between 0 and 1023, not 1024"+ indicesToMnemonic [-1] `shouldBe` Left "The word index must be between 0 and 1023, not -1"++ describe "the field GF(256)" $ do+ it "multiplies 0x57 by 0x83 to 0xc1, the example of FIPS 197" $+ gfMul 0x57 0x83 `shouldBe` 0xc1+ it "multiplies 0x57 by 0x13 to 0xfe, the other example of FIPS 197" $+ gfMul 0x57 0x13 `shouldBe` 0xfe+ it "multiplies 2 by 0x80 to 0x1b: 0x100 reduced by x^8 + x^4 + x^3 + x + 1" $+ gfMul 2 0x80 `shouldBe` 0x1b+ it "multiplies by zero to zero and by one to the byte itself" $ property $ \a ->+ (gfMul a 0, gfMul 0 a, gfMul a 1, gfMul 1 a) === (0, 0, a, a)+ it "commutes" $ property $ \a b ->+ gfMul a b === gfMul b a+ it "inverts 0x53 to 0xca, the example of the AES S-box" $ do+ gfInv 0x53 `shouldBe` Just 0xca+ gfMul 0x53 0xca `shouldBe` 1+ it "inverts 3, the generator, to 0xf6" $+ gfInv 3 `shouldBe` Just 0xf6+ it "has no inverse of zero" $ do+ gfInv 0 `shouldBe` Nothing+ gfDiv 1 0 `shouldBe` Nothing+ it "multiplies every non-zero byte by its inverse to one" $+ forM_ [1 .. 255] $ \a -> (gfMul a <$> gfInv a) `shouldBe` Just 1+ it "divides 0xc1 by 0x83 to 0x57" $+ gfDiv 0xc1 0x83 `shouldBe` Just 0x57++ describe "interpolation" $ do+ it "takes the line through (0, 0x00) and (1, 0x80) to (2, 0x1b)" $+ -- f(x) = 0x80 x, and 2 times 0x80 is 0x1b in the field+ interpolate 2 [(0, "\x00"), (1, "\x80")] `shouldBe` Right "\x1b"+ it "takes that line to (3, 0x9b)" $+ -- 3 times 0x80 is 0x180, reduced by 0x11b to 0x9b+ interpolate 3 [(0, "\x00"), (1, "\x80")] `shouldBe` Right "\x9b"+ it "takes the line through (1, 0x01) and (2, 0x03) to (0, 0xf6) and (3, 0xf4)" $ do+ -- the slope is (3 xor 1) / (2 xor 1) = 2 / 3 = 2 times 0xf6 = 0xf7, so+ -- f(0) = 1 xor 0xf7 = 0xf6 and f(3) = 0xf6 xor 3 times 0xf7 = 0xf6 xor 2+ interpolate 0 [(1, "\x01"), (2, "\x03")] `shouldBe` Right "\xf6"+ interpolate 3 [(1, "\x01"), (2, "\x03")] `shouldBe` Right "\xf4"+ it "interpolates each byte on its own" $+ -- the first byte as the line through 0x00 and 0x80, the second+ -- through 0x00 and 0x01: f(2) = 2+ interpolate 2 [(0, "\x00\x00"), (1, "\x80\x01")] `shouldBe` Right "\x1b\x02"+ it "gives the constant of a constant polynomial anywhere" $+ interpolate 200 [(5, B.pack [0 .. 15]), (9, B.pack [0 .. 15])] `shouldBe` Right (B.pack [0 .. 15])+ it "gives a point its own value" $+ interpolate 1 [(0, "\x00"), (1, "\x80")] `shouldBe` Right "\x80"+ it "refuses no points" $+ interpolate 1 [] `shouldBe` Left "There are no shares to interpolate"+ it "refuses two points at one index" $+ interpolate 2 [(1, "\x01"), (1, "\x03")] `shouldBe` Left "The shares do not have distinct indices"+ it "refuses values of different lengths" $+ interpolate 2 [(0, "\x01"), (1, "\x03\x04")] `shouldBe` Left "The shares do not have the same length"++ describe "the checksum" $ do+ -- The first vector: 17 data words then "critical decision keyboard"+ let vector1Words = [248, 288, 0, 0, 17, 753, 521, 840, 372, 497, 155, 670, 192, 448, 297, 249, 23]+ it "makes the three words critical decision keyboard for the first vector" $ do+ rs1024Checksum (customizationString False) vector1Words `shouldBe` [173, 196, 496]+ indicesToMnemonic [173, 196, 496] `shouldBe` Right "critical decision keyboard"+ it "verifies the first vector" $+ rs1024Verify (customizationString False) (vector1Words ++ [173, 196, 496]) `shouldBe` True+ it "fails when the last word changes to kidney" $+ rs1024Verify (customizationString False) (vector1Words ++ [173, 196, 497]) `shouldBe` False+ it "has a polymod of 1 for a valid mnemonic after its customization string" $+ rs1024Polymod (customizationString False ++ vector1Words ++ [173, 196, 496]) `shouldBe` 1+ it "feeds shamir or shamir_extendable to the polymod as ASCII" $ do+ customizationString False `shouldBe` map ord "shamir"+ customizationString True `shouldBe` map ord "shamir_extendable"+ it "verifies an extendable share under shamir_extendable only" $ do+ -- Vector 42, an extendable share+ let indices = indicesOf (mnemonicOf vectors 42)+ rs1024Verify (customizationString True) indices `shouldBe` True+ rs1024Verify (customizationString False) indices `shouldBe` False+ it "verifies every mnemonic of the valid vectors" $+ forM_ [(d, m) | (d, ms, s, _) <- vectors, s /= "", m <- ms] $ \(d, m) -> do+ let indices = indicesOf m+ -- the extendable flag is bit 24 of the 40-bit header, bit 4 of the second word+ ext = odd ((indices !! 1) `div` 16)+ (d, rs1024Verify (customizationString ext) indices) `shouldBe` (d, True)++ describe "share mnemonics" $ do+ it "decodes the share of the first vector" $+ decodeShare (mnemonicOf vectors 1) `shouldBe` Right vector1+ it "encodes it back to its 20 words" $+ encodeShare vector1 `shouldBe` Right (mnemonicOf vectors 1)+ it "decodes an extendable share with an iteration exponent of 3" $+ -- Vector 42+ decodeShare (mnemonicOf vectors 42)+ `shouldBe` Right (singleShare 29019 True 3 (hex "9e8773c7313b11d3bfe219291976433b"))+ it "decodes a share of 33 words as 32 bytes of value" $+ -- Vector 20+ decodeShare (mnemonicOf vectors 20)+ `shouldBe` Right (singleShare 29172 False 0 (hex "d772fee46424e100bec16d165f1fcc346d1e8d909da580f9f9f04ea5c788d212"))+ it "encodes a 32-byte value as 33 words and a 16-byte one as 20" $ do+ fmap (length . T.words) (encodeShare vector1) `shouldBe` Right 20+ fmap (length . T.words) (encodeShare vector1 {value = B.replicate 32 0}) `shouldBe` Right 33+ it "encodes a 20-byte value as 23 words, with no padding" $+ -- 160 bits is 16 words exactly+ fmap (length . T.words) (encodeShare vector1 {value = B.replicate 20 0}) `shouldBe` Right 23+ it "reads words in upper case" $+ decodeShare (T.toUpper (mnemonicOf vectors 1)) `shouldBe` Right vector1+ it "refuses 19 words" $+ -- Vector 39+ decodeShare (mnemonicOf vectors 39) `shouldBe` Left "The mnemonic has 19 words, but a share has at least 20"+ it "refuses 21 words, which would leave 12 bits of padding" $+ -- Vector 40+ decodeShare (mnemonicOf vectors 40) `shouldBe` Left "The mnemonic has 21 words, which is not a valid share length"+ it "refuses 24 words, which would leave 10 bits of padding" $+ -- 17 words of value are 170 bits, and 170 modulo 16 is 10. The+ -- checksum words are those of the reference implementation, so the+ -- length is the only thing wrong.+ decodeShare (T.unwords (["duckling", "enlarge"] ++ replicate 19 "academic" ++ ["diminish", "hearing", "undergo"]))+ `shouldBe` Left "The mnemonic has 24 words, which is not a valid share length"+ it "refuses 1025 words, the first share length above the 1023 words the checksum covers" $+ -- A Reed-Solomon code over GF(1024) has at most 1023 symbols. This is+ -- the share of 1272 zero bytes as the reference implementation writes+ -- it, with its checksum, so the length is the only thing wrong. 1024+ -- words are no share length: they would leave 10 bits of padding.+ decodeShare (T.unwords (["duckling", "enlarge"] ++ replicate 1020 "academic" ++ ["sack", "sugar", "course"]))+ `shouldBe` Left "The mnemonic has 1025 words, but a share has at most 1023"+ it "refuses a share of 256,007 words with a valid checksum within a second" $ do+ -- The header of the first vector, then 256,000 words of value with+ -- every bit set, which leave no padding, then their checksum. Such a+ -- share used to have its words turned into one number, in time that+ -- grew with the square of the length: 1.8 seconds for half as many+ -- words. The checksum is computed here because the share is the+ -- input of the test, not its expectation.+ let dataWords = [248, 288, 0, 0] ++ replicate 256000 1023+ checksum = rs1024Checksum (customizationString False) dataWords+ mnemonic = T.unwords (map (slip39WordList V.!) (dataWords ++ checksum))+ _ <- evaluate (T.length mnemonic)+ -- the length of the value forces its bytes+ timeout 1000000 (evaluate (force (fmap (\s -> B.length s.value) (decodeShare mnemonic))))+ `shouldReturn` Just (Left "The mnemonic has 256007 words, but a share has at most 1023")+ it "encodes a value of 1270 bytes as 1023 words, the longest share, and reads it back" $ do+ -- (1023 - 7) words of 10 bits are 10160 bits, 1270 bytes. The three+ -- checksum words are those of the reference implementation.+ let longest = vector1 {value = B.replicate 1270 0}+ mnemonic = encodeShare longest+ fmap (length . T.words) mnemonic `shouldBe` Right 1023+ fmap (drop 1020 . T.words) mnemonic `shouldBe` Right ["academic", "shaped", "dictate"]+ (mnemonic >>= decodeShare) `shouldBe` Right longest+ it "refuses to encode a value of 1272 bytes, which would take 1025 words" $+ encodeShare vector1 {value = B.replicate 1272 0}+ `shouldBe` Left "The share value must have at most 1270 bytes, not 1272"+ it "refuses a wrong checksum" $+ -- Vector 2: the first vector with its last word changed+ decodeShare (mnemonicOf vectors 2) `shouldBe` Left "The checksum of the mnemonic is invalid"+ it "refuses padding bits that are not zero" $+ -- Vector 3+ decodeShare (mnemonicOf vectors 3) `shouldBe` Left "The padding bits of the share value are not zero"+ it "refuses a group threshold above the group count" $+ -- Vector 10, first share+ decodeShare (mnemonicOf vectors 10) `shouldBe` Left "The group threshold is greater than the group count"+ it "refuses a word that is not in the list" $+ decodeShare "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision abandon"+ `shouldBe` Left "Word 20 is not in the SLIP-39 word list"+ it "refuses to encode an identifier of 16 bits" $+ encodeShare (singleShare 32768 False 0 vector1.value) `shouldBe` Left "The identifier must be between 0 and 32767, not 32768"+ it "refuses to encode an iteration exponent of 16" $+ encodeShare (singleShare 7945 False 16 vector1.value) `shouldBe` Left "The iteration exponent must be between 0 and 15, not 16"+ it "refuses to encode a group index of 16" $+ encodeShare vector1 {groupIndex = 16} `shouldBe` Left "The group index must be between 0 and 15, not 16"+ it "refuses to encode a group threshold of 0" $+ encodeShare (groupShare 0 1 vector1.value) `shouldBe` Left "The group threshold must be between 1 and 16, not 0"+ it "refuses to encode a group count of 17" $+ encodeShare vector1 {groupCount = 17} `shouldBe` Left "The group count must be between 1 and 16, not 17"+ it "refuses to encode a group threshold above the group count" $+ encodeShare (groupShare 2 1 vector1.value) `shouldBe` Left "The group threshold is greater than the group count"+ it "refuses to encode a member index of 16" $+ encodeShare vector1 {memberIndex = 16} `shouldBe` Left "The member index must be between 0 and 15, not 16"+ it "refuses to encode a member threshold of 17" $+ encodeShare vector1 {memberThreshold = 17} `shouldBe` Left "The member threshold must be between 1 and 16, not 17"+ it "refuses to encode an identifier of -1" $+ encodeShare (singleShare (-1) False 0 vector1.value) `shouldBe` Left "The identifier must be between 0 and 32767, not -1"+ it "refuses to encode an iteration exponent of -1" $+ encodeShare (singleShare 7945 False (-1) vector1.value) `shouldBe` Left "The iteration exponent must be between 0 and 15, not -1"+ it "refuses to encode a group index of -1" $+ encodeShare vector1 {groupIndex = -1} `shouldBe` Left "The group index must be between 0 and 15, not -1"+ it "refuses to encode a group count of 0" $+ encodeShare (groupShare 1 0 vector1.value) `shouldBe` Left "The group count must be between 1 and 16, not 0"+ it "refuses to encode a member index of -1" $+ encodeShare vector1 {memberIndex = -1} `shouldBe` Left "The member index must be between 0 and 15, not -1"+ it "refuses to encode a member threshold of 0" $+ encodeShare vector1 {memberThreshold = 0} `shouldBe` Left "The member threshold must be between 1 and 16, not 0"+ it "refuses to encode a value of 15 bytes" $+ encodeShare vector1 {value = B.replicate 15 0} `shouldBe` Left "The share value must have at least 16 bytes, not 15"+ it "refuses to encode a value of 17 bytes" $+ encodeShare vector1 {value = B.replicate 17 0} `shouldBe` Left "The share value must have an even number of bytes, not 17"+ it "shows a share without its value" $+ show vector1+ `shouldBe` "Share {identifier = 7945, extendable = False, iterationExponent = 0, groupIndex = 0, groupThreshold = 1, groupCount = 1, memberIndex = 0, memberThreshold = 1, value = <redacted>}"++ describe "splitting and recovering a secret" $ do+ let secret = "ABCDEFGHIJKLMNOP"+ digestRandom = B.pack [0 .. 11]+ noRandom = SplitRandom [] B.empty+ -- 2-of-3 of the secret with that random part: the verifier's values+ share0 = (0, hex "8459b115d432d65461876350dc3ade25")+ share1 = (1, hex "2b463c939175931f2cc82e1b997d9b7e")+ share2 = (2, hex "c167b0025ebc5cc2fb19f9c656b45493")+ twoOfThree = [share0, share1, share2]+ it "gives the secret itself as every share at a threshold of 1" $+ splitSecret 1 3 secret noRandom `shouldBe` Right [(0, secret), (1, secret), (2, secret)]+ it "splits 2-of-3 as the verifier does, from the random part of the digest share" $+ splitSecret 2 3 secret (SplitRandom [] digestRandom) `shouldBe` Right twoOfThree+ it "recovers the secret from any two of the three" $+ forM_ [[share0, share1], [share1, share2], [share0, share2]] $ \two ->+ recoverSecret 2 two `shouldBe` Right secret+ it "recovers it from all three as well" $+ recoverSecret 2 twoOfThree `shouldBe` Right secret+ it "splits 3-of-4 as the verifier does, from a random share and the random part" $+ -- The random share takes index 0 as it is+ splitSecret 3 4 secret (SplitRandom [B.pack [0x10 .. 0x1f]] (B.pack [0x20 .. 0x2b]))+ `shouldBe` Right+ [ (0, hex "101112131415161718191a1b1c1d1e1f"),+ (1, hex "7d29eb177172737c75767770797a7b64"),+ (2, hex "009af86a90059252089d0aac980d9a96"),+ (3, hex "6da2016ef562f73965f267c7fd6affed")+ ]+ it "refuses the digest when a share of another secret is mixed in" $+ -- the share at index 1 of the 2-of-3 of "abcdefghijklmnop" with the same random part+ recoverSecret 2 [share0, (1, hex "b086c5a043a741cdfe1afcc94baf49ac")]+ `shouldBe` Left "The digest of the secret does not match"+ it "refuses the digest when a share is corrupted" $+ recoverSecret 2 [share0, (1, hex "2b463c939175931f2cc82e1b997d9b7f")]+ `shouldBe` Left "The digest of the secret does not match"+ it "recovers the first share at a threshold of 1" $+ recoverSecret 1 [(4, secret), (5, "ponmlkjihgfedcba")] `shouldBe` Right secret+ it "refuses no shares" $+ recoverSecret 2 [] `shouldBe` Left "There are no shares to recover the secret from"+ it "refuses a threshold of 0" $+ splitSecret 0 3 secret noRandom `shouldBe` Left "The threshold must be at least 1"+ it "refuses a threshold above the share count" $+ splitSecret 4 3 secret noRandom `shouldBe` Left "The threshold is greater than the number of shares"+ it "refuses 17 shares" $+ splitSecret 2 17 secret noRandom `shouldBe` Left "The number of shares is greater than 16"+ it "refuses a secret of 15 bytes" $+ splitSecret 1 1 (B.take 15 secret) noRandom `shouldBe` Left "The secret must have at least 16 bytes, not 15"+ it "refuses a secret of 17 bytes" $+ splitSecret 1 1 (secret <> "Q") noRandom `shouldBe` Left "The secret must have an even number of bytes, not 17"+ it "refuses random shares at a threshold of 1" $+ splitSecret 1 1 secret (SplitRandom [secret] B.empty) `shouldBe` Left "A split with a threshold of 1 takes 0 random shares"+ it "refuses a random part at a threshold of 1" $+ splitSecret 1 1 secret (SplitRandom [] digestRandom) `shouldBe` Left "The random part of the digest share must have 0 bytes"+ it "refuses too few random shares" $+ splitSecret 3 4 secret (SplitRandom [] digestRandom) `shouldBe` Left "A split with a threshold of 3 takes 1 random shares"+ it "refuses a random share of another length" $+ splitSecret 3 4 secret (SplitRandom [B.take 15 secret] digestRandom) `shouldBe` Left "A random share must be as long as the secret"+ it "refuses a random part of another length" $+ splitSecret 2 3 secret (SplitRandom [] (B.take 11 digestRandom)) `shouldBe` Left "The random part of the digest share must have 12 bytes"+ it "shows the random values of a split without their bytes" $+ -- the random shares are real shares, and the random part is most of+ -- the digest share+ show (SplitRandom [B.replicate 16 0x41] (B.replicate 12 0x42))+ `shouldBe` "SplitRandom {randomShares = <redacted>, digestRandom = <redacted>}"+ it "sizes the random values: none at threshold 1, T - 2 shares and n - 4 bytes otherwise" $ do+ splitRandomSizes 1 16 `shouldBe` (0, 16, 0)+ splitRandomSizes 2 16 `shouldBe` (0, 16, 12)+ splitRandomSizes 5 32 `shouldBe` (3, 32, 28)+ it "recovers any random secret from any threshold of its shares" $+ property $ forAll ((,) <$> bytesOf 16 <*> bytesOf 12) $ \(s, r) ->+ case splitSecret 2 3 s (SplitRandom [] r) of+ Right shares -> all ((== Right s) . recoverSecret 2) [two | two <- subsequences shares, length two == 2]+ Left _ -> False++ describe "the passphrase" $ do+ let secret = "ABCDEFGHIJKLMNOP"+ ems = EncryptedMasterSecret {identifier = 7945, extendable = False, iterationExponent = 0, ciphertext = hex "11bc609d21747c49ba78c0701293e417"}+ it "decrypts the encrypted master secret of the first vector with TREZOR" $+ decryptMasterSecret "TREZOR" ems `shouldBe` Right (hex "bb54aac4b89dc868ba37d9cc21b2cece")+ it "encrypts that master secret back" $+ encryptMasterSecret 7945 False 0 "TREZOR" (hex "bb54aac4b89dc868ba37d9cc21b2cece") `shouldBe` Right ems+ it "encrypts ABCDEFGHIJKLMNOP under an empty passphrase as the verifier does" $+ fmap (.ciphertext) (encryptMasterSecret 1 True 0 "" secret) `shouldBe` Right (hex "7b78e5f690282c25aa183fa39a23dd02")+ it "ignores the identifier when the backup is extendable" $+ fmap (.ciphertext) (encryptMasterSecret 2 True 0 "" secret) `shouldBe` Right (hex "7b78e5f690282c25aa183fa39a23dd02")+ it "salts with the identifier when the backup is not extendable" $ do+ fmap (.ciphertext) (encryptMasterSecret 1 False 0 "" secret) `shouldBe` Right (hex "3162b7863d3710ef05f35feaa08fbc54")+ fmap (.ciphertext) (encryptMasterSecret 2 False 0 "" secret) `shouldBe` Right (hex "90330cd4eef5857777c6a09793195a23")+ it "gives another ciphertext under another passphrase" $+ fmap (.ciphertext) (encryptMasterSecret 1 True 0 "TREZOR" secret) `shouldBe` Right (hex "c5462e7421d4c690de1668d6a3bfd337")+ it "gives another ciphertext under another iteration exponent" $+ fmap (.ciphertext) (encryptMasterSecret 1 True 1 "" secret) `shouldBe` Right (hex "d51a2d68cb0adf27958873071f5253ac")+ it "decrypts to another master secret under a wrong passphrase, without an error" $+ decryptMasterSecret "trezor" ems `shouldSatisfy` \r -> r /= Right (hex "bb54aac4b89dc868ba37d9cc21b2cece") && either (const False) (const True) r+ it "accepts the first and the last printable ASCII characters" $+ fmap (const ()) (encryptMasterSecret 1 True 0 " ~" secret) `shouldBe` Right ()+ it "refuses a passphrase outside printable ASCII" $+ encryptMasterSecret 1 True 0 "café" secret+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ it "refuses a control character in the passphrase" $+ encryptMasterSecret 1 True 0 "a\tb" secret+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ it "refuses the characters next to printable ASCII: U+001F and U+007F" $ do+ encryptMasterSecret 1 True 0 "a\x1f" secret+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ encryptMasterSecret 1 True 0 "a\x7f" secret+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ it "refuses a passphrase outside printable ASCII on decryption too" $ do+ -- "the passphrase MUST be a string containing only printable ASCII+ -- characters" (SLIP-39). Two spellings of one visible passphrase+ -- used to open two wallets.+ decryptMasterSecret "caf\x00e9" cafeEms+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ decryptMasterSecret "cafe\x0301" cafeEms+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ combineMnemonics "caf\x00e9" [cafeMnemonic]+ `shouldBe` Left "The passphrase must contain only printable ASCII characters, code points 32 to 126"+ it "decrypts with the bytes of a passphrase of other software, which it does not check" $ do+ -- The share was made by version 0.3.0 of the reference implementation+ -- with "café" as UTF-8, 63 61 66 c3 a9. The second line is what the+ -- reference gives for "e" followed by U+0301: another secret.+ decryptMasterSecretBytes (B.pack [0x63, 0x61, 0x66, 0xc3, 0xa9]) cafeEms `shouldBe` Right secret+ fmap encodeHex (decryptMasterSecretBytes (B.pack [0x63, 0x61, 0x66, 0x65, 0xcc, 0x81]) cafeEms)+ `shouldBe` Right "7a1965e4e0f2c5efee3e7efe53222345"+ it "rebuilds the encrypted master secret of that share, whatever its passphrase was" $+ (decodeShare cafeMnemonic >>= \s -> combineShares [s]) `shouldBe` Right cafeEms+ it "refuses to decrypt under an iteration exponent of 16" $+ decryptMasterSecret "" EncryptedMasterSecret {identifier = 7945, extendable = False, iterationExponent = 16, ciphertext = hex "11bc609d21747c49ba78c0701293e417"}+ `shouldBe` Left "The iteration exponent must be between 0 and 15, not 16"+ it "refuses to decrypt under an identifier of 16 bits" $+ decryptMasterSecret "" EncryptedMasterSecret {identifier = 32768, extendable = False, iterationExponent = 0, ciphertext = hex "11bc609d21747c49ba78c0701293e417"}+ `shouldBe` Left "The identifier must be between 0 and 32767, not 32768"+ it "refuses a master secret of 15 bytes" $+ encryptMasterSecret 1 True 0 "" (B.take 15 secret) `shouldBe` Left "The master secret must have at least 16 bytes, not 15"+ it "refuses a master secret of 17 bytes" $+ encryptMasterSecret 1 True 0 "" (secret <> "Q") `shouldBe` Left "The master secret must have an even number of bytes, not 17"+ it "refuses an identifier of 16 bits" $+ encryptMasterSecret 32768 True 0 "" secret `shouldBe` Left "The identifier must be between 0 and 32767, not 32768"+ it "refuses an iteration exponent of 16" $+ encryptMasterSecret 1 True 16 "" secret `shouldBe` Left "The iteration exponent must be between 0 and 15, not 16"+ it "refuses to decrypt a ciphertext of 15 bytes" $+ decryptMasterSecret "" ems {ciphertext = B.take 15 ems.ciphertext} `shouldBe` Left "The encrypted master secret must have at least 16 bytes, not 15"+ it "shows an encrypted master secret without its ciphertext" $+ show ems `shouldBe` "EncryptedMasterSecret {identifier = 7945, extendable = False, iterationExponent = 0, ciphertext = <redacted>}"+ it "decrypts what it encrypts, for any secret and passphrase" $+ property $ forAll ((,) <$> bytesOf 16 <*> listOf (elements [' ' .. '~'])) $ \(s, p) ->+ (encryptMasterSecret 5 True 0 (T.pack p) s >>= decryptMasterSecret (T.pack p)) === Right s++ describe "generating shares" $ do+ forM_ splitCases $ \c -> do+ it ("splits " <> T.unpack c.caseDescription <> " as the verifier does") $+ (generateSharesWith c.caseScheme c.caseIdentifier c.caseExtendable c.caseIterationExponent c.casePassphrase c.caseMasterSecret c.caseRandom >>= traverse (traverse encodeShare))+ `shouldBe` Right c.caseMnemonics+ it ("combines any threshold of the shares of " <> T.unpack c.caseDescription) $+ forM_ (thresholdSubsets c) $ \subset ->+ combineMnemonics c.casePassphrase subset `shouldBe` Right c.caseMasterSecret+ it "gives the 2-of-3 case these three mnemonics" $+ -- The second split case, written out: the first three words are the+ -- identifier 42, the flag and the exponent 1; the fourth differs by+ -- the member index+ generateSharesWith (Scheme 1 [(2, 3)]) 42 True 1 "TREZOR" "ABCDEFGHIJKLMNOP" [SplitRandom [] B.empty, SplitRandom [] (hex "698c2ccb37ff81b7f5603f9b")]+ `shouldBe` Right+ [ [ share 42 True 1 0 1 1 0 2 "acid fawn academic acid club member living lawsuit sunlight pancake market lend peasant sunlight glad simple infant organize grant infant",+ share 42 True 1 0 1 1 1 2 "acid fawn academic agency bracelet transfer march argue wits exact dough parcel animal traveler dictate river military salt satisfy payment",+ share 42 True 1 0 1 1 2 2 "acid fawn academic always advocate either wine oasis solution goat toxic injury radar romantic shrimp check warmth fangs ending mineral"+ ]+ ]+ it "refuses a scheme without groups" $+ generateSharesWith (Scheme 1 []) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "A scheme has at least one group"+ it "refuses a scheme of 17 groups" $+ generateSharesWith (Scheme 1 (replicate 17 (1, 1))) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "A scheme has at most 16 groups"+ it "refuses a group threshold of 0" $+ generateSharesWith (Scheme 0 [(1, 1)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "The group threshold must be between 1 and 1, not 0"+ it "refuses a group threshold above the number of groups" $+ generateSharesWith (Scheme 3 [(1, 1), (1, 1)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "The group threshold must be between 1 and 2, not 3"+ it "refuses a member count of 17" $+ generateSharesWith (Scheme 1 [(2, 17)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "The member count of group 1 must be between 1 and 16, not 17"+ it "refuses a member threshold of 0" $+ generateSharesWith (Scheme 1 [(0, 3)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "The member threshold of group 1 must be between 1 and 3, not 0"+ it "refuses a member threshold above the member count" $+ generateSharesWith (Scheme 1 [(1, 1), (4, 3)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "The member threshold of group 2 must be between 1 and 3, not 4"+ it "refuses a group of several members with a member threshold of 1" $+ generateSharesWith (Scheme 1 [(1, 3)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [] `shouldBe` Left "A group with a member threshold of 1 has one member, but group 1 has 3"+ it "refuses random values for the wrong number of splits" $+ generateSharesWith (Scheme 1 [(1, 1)]) 1 True 0 "" "ABCDEFGHIJKLMNOP" [SplitRandom [] B.empty]+ `shouldBe` Left "A scheme of 1 groups takes 2 sets of random values: one for the split among the groups, then one per group"+ it "asks its source for nothing when the master secret has 15 bytes" $+ runState (generateSharesFrom recordedZeros (Scheme 1 [(2, 3)]) True 0 "" "ABCDEFGHIJKLMNO") []+ `shouldBe` (Left "The master secret must have at least 16 bytes, not 15", [])+ it "asks its source for nothing when the scheme, the exponent or the passphrase is refused" $ do+ runState (generateSharesFrom recordedZeros (Scheme 1 [(1, 3)]) True 0 "" "ABCDEFGHIJKLMNOP") []+ `shouldBe` (Left "A group with a member threshold of 1 has one member, but group 1 has 3", [])+ runState (generateSharesFrom recordedZeros (Scheme 1 [(2, 3)]) True 16 "" "ABCDEFGHIJKLMNOP") []+ `shouldBe` (Left "The iteration exponent must be between 0 and 15, not 16", [])+ runState (generateSharesFrom recordedZeros (Scheme 1 [(2, 3)]) True 0 "caf\x00e9" "ABCDEFGHIJKLMNOP") []+ `shouldBe` (Left "The passphrase must contain only printable ASCII characters, code points 32 to 126", [])+ it "asks its source for 2 and 12 bytes to split 16 bytes 2-of-3" $+ -- 2 for the identifier, nothing for the split among one group, and+ -- 16 - 4 = 12 for the random part of the digest share+ snd (runState (generateSharesFrom recordedZeros (Scheme 1 [(2, 3)]) True 0 "" "ABCDEFGHIJKLMNOP") [])+ `shouldBe` [2, 12]+ it "asks its source for 2, 32 and 28 bytes to split 32 bytes 3-of-5" $+ -- 2 for the identifier, 32 for the one random share, and 32 - 4 = 28+ -- for the random part of the digest share+ snd (runState (generateSharesFrom recordedZeros (Scheme 1 [(3, 5)]) True 0 "" "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345") [])+ `shouldBe` [2, 32, 28]+ it "refuses a source that gives fewer bytes than asked" $+ fst (runState (generateSharesFrom (\_ -> return "x") (Scheme 1 [(2, 3)]) True 0 "" "ABCDEFGHIJKLMNOP") ())+ `shouldBe` Left "The source of random bytes gave 1 bytes where 2 were asked for"+ it "draws a 15-bit identifier and shares of which any two of three rebuild the secret" $ do+ let secret = "ABCDEFGHIJKLMNOP"+ drawn = fst (withDRG (drgNewTest (1, 2, 3, 4, 5)) (generateSharesFrom getRandomBytes (Scheme 1 [(2, 3)]) True 0 "" secret))+ case drawn of+ Right [shares] -> do+ length shares `shouldBe` 3+ length (nub (map (.identifier) shares)) `shouldBe` 1+ forM_ shares $ \s -> do+ s.identifier `shouldSatisfy` (\i -> i >= 0 && i <= 32767)+ (s.extendable, s.iterationExponent, s.groupIndex, s.groupThreshold, s.groupCount, s.memberThreshold) `shouldBe` (True, 0, 0, 1, 1, 2)+ map (.memberIndex) shares `shouldBe` [0, 1, 2]+ forM_ [two | two <- subsequences shares, length two == 2] $ \two ->+ (combineShares two >>= decryptMasterSecret "") `shouldBe` Right secret+ other -> expectationFailure ("expected one group of three shares, got " <> show other)+ it "draws the same shares from the same random state and others from another" $ do+ let draw seed = fst (withDRG (drgNewTest seed) (generateSharesFrom getRandomBytes (Scheme 1 [(2, 3)]) True 0 "" "ABCDEFGHIJKLMNOP"))+ draw (1, 2, 3, 4, 5) `shouldBe` draw (1, 2, 3, 4, 5)+ draw (1, 2, 3, 4, 5) `shouldNotBe` draw (6, 7, 8, 9, 10)+ it "rebuilds a secret of 16 to 64 bytes from a threshold of the shares of any small scheme" $+ property $ forAll splitOfAnyScheme $ \(scheme, ext, passphrase, secret, drawn) ->+ case drawn of+ Left err -> counterexample err False+ Right groups ->+ -- the first groups and the first members that meet the+ -- thresholds, then the last ones+ let firstOnes = concat [take t members | ((t, _), members) <- take scheme.groupThreshold (zip scheme.groups groups)]+ lastOnes = concat [lastN t members | ((t, _), members) <- lastN scheme.groupThreshold (zip scheme.groups groups)]+ rebuilt shares = combineShares shares >>= decryptMasterSecret passphrase+ in (rebuilt firstOnes, rebuilt lastOnes) === (Right secret, Right secret)+ it "refuses a share with one word of its value or checksum changed" $+ -- the checksum catches any change of up to three words+ property $ forAll changedWord $ \(position, word) ->+ let ws = T.words (mnemonicOf vectors 1)+ changed = T.unwords (take position ws ++ [word] ++ drop (position + 1) ws)+ in ws !! position /= word ==> decodeShare changed === Left "The checksum of the mnemonic is invalid"+ it "draws shares with the system's randomness that rebuild a two-level scheme" $ do+ let secret = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"+ drawn <- generateMnemonics (Scheme 2 [(1, 1), (2, 3), (3, 5)]) True 0 "pass" secret+ case drawn of+ Right [[a], bs, cs] -> do+ combineMnemonics "pass" (a : take 2 bs) `shouldBe` Right secret+ combineMnemonics "pass" (take 2 bs ++ take 3 cs) `shouldBe` Right secret+ combineMnemonics "pass" (a : drop 2 cs) `shouldBe` Right secret+ combineMnemonics "pass" (a : take 1 bs) `shouldBe` Left "Group 1 has 1 share, but its member threshold is 2"+ other -> expectationFailure ("expected groups of 1, 3 and 5 shares, got " <> show other)++ describe "combining shares" $ do+ it "rebuilds the encrypted master secret of the first vector from its one share" $+ combineShares [vector1]+ `shouldBe` Right EncryptedMasterSecret {identifier = 7945, extendable = False, iterationExponent = 0, ciphertext = hex "11bc609d21747c49ba78c0701293e417"}+ it "refuses no shares" $+ combineShares [] `shouldBe` Left "There are no shares to combine"+ it "refuses no mnemonics" $+ combineMnemonics "" [] `shouldBe` Left "There are no shares to combine"+ it "refuses shares that differ on the extendable flag" $+ combineShares [vector1, (singleShare 7945 True 0 vector1.value) {memberIndex = 1}] `shouldBe` Left "The shares do not have the same extendable flag"+ it "refuses shares whose values differ in length" $+ combineShares [vector1, vector1 {memberIndex = 1, value = B.replicate 32 0}] `shouldBe` Left "The shares do not have the same length of their values"+ it "refuses a share with a field out of range" $+ combineShares [vector1 {memberIndex = 16}] `shouldBe` Left "The member index must be between 0 and 15, not 16"+ it "refuses more groups than the group threshold" $+ combineShares [groupShare 1 2 vector1.value, (groupShare 1 2 vector1.value) {groupIndex = 1}]+ `shouldBe` Left "The shares come from 2 groups, but the group threshold is 1"+ it "refuses more shares than the member threshold of a group" $+ combineShares [vector1, vector1 {memberIndex = 1}] `shouldBe` Left "Group 0 has 2 shares, but its member threshold is 1"++ describe "the shares of another implementation" $ do+ -- From the tests of Sparrow's library: eight groups of one member, five+ -- of them needed, and the value of the fifth share starts with a zero+ -- byte, which a conversion through a number would drop+ let sparrow =+ [ "budget surprise agency romp clay quiet spew favorite purchase thumb observe sympathy acrobat presence unfold focus already tricycle diagnose muscle",+ "budget surprise birthday romp breathe scroll intend humidity aspect plains isolate crystal blimp dragon garbage both kind dance human craft",+ "budget surprise cleanup romp early remove uncover hearing lamp firefly ounce species submit snake phrase genuine tactics mustang desire artist",+ "budget surprise desert romp deny easy senior arena smirk average single smart fatal lecture threaten eyebrow lunar carve indicate romantic",+ "budget surprise email romp academic anxiety sister nylon fake makeup spine breathe window true recall desktop shelter divorce luck screw"+ ]+ it "keeps the zero byte at the head of a share value" $+ fmap (\s -> (s.groupIndex, s.groupThreshold, s.groupCount, s.memberThreshold, encodeHex s.value)) (decodeShare (last sparrow))+ `shouldBe` Right (4, 5, 8, 1, "000bf319a1478ab5418bedeb2de34f26")+ it "rebuilds the master secret from five of the eight groups" $+ fmap encodeHex (combineMnemonics "" sparrow) `shouldBe` Right "609d9cf21c065a88e9f5b97c0e6928c6"++ describe "the vectors of the reference implementation" $ do+ it "has 45 vectors, 15 of them valid" $ do+ length vectors `shouldBe` 45+ length [() | (_, _, s, _) <- vectors, s /= ""] `shouldBe` 15+ forM_ (zip [1 :: Int ..] vectors) $ \(n, (d, ms, secret, xprv)) ->+ if secret /= ""+ then it (T.unpack d <> ": gives the master secret and its BIP-32 key") $ do+ combineMnemonics "TREZOR" ms `shouldBe` Right (hex secret)+ xPrvExport btc (makeXPrvKey (hex secret)) `shouldBe` xprv+ else it (T.unpack d <> ": is refused") $+ combineMnemonics "TREZOR" ms `shouldBe` Left (refusalOf n)++-- | A share made by version 0.3.0 of the reference implementation with the+-- passphrase "café", from its tests. Its master secret is ABCDEFGHIJKLMNOP.+cafeMnemonic :: Text+cafeMnemonic = "acid fawn academic academic company fridge picture cage pecan glance privacy carpet should public pharmacy eclipse percent python together flavor"++-- | The encrypted master secret of 'cafeMnemonic', as the reference+-- implementation reads it: a 1-of-1 share is the encrypted secret itself.+cafeEms :: EncryptedMasterSecret+cafeEms = EncryptedMasterSecret {identifier = 42, extendable = True, iterationExponent = 1, ciphertext = hex "9f5d29d1ca9062eb91fb29b0e9840a93"}++-- | The share of the first vector.+vector1 :: Share+vector1 = singleShare 7945 False 0 (hex "11bc609d21747c49ba78c0701293e417")++-- | A share of a 1-of-1 split with the identifier, the flag, the exponent+-- and the value given.+singleShare :: Int -> Bool -> Int -> ByteString -> Share+singleShare i ext e v =+ Share+ { identifier = i,+ extendable = ext,+ iterationExponent = e,+ groupIndex = 0,+ groupThreshold = 1,+ groupCount = 1,+ memberIndex = 0,+ memberThreshold = 1,+ value = v+ }++-- | The first vector's share with the group threshold and count given.+groupShare :: Int -> Int -> ByteString -> Share+groupShare threshold count v =+ Share+ { identifier = 7945,+ extendable = False,+ iterationExponent = 0,+ groupIndex = 0,+ groupThreshold = threshold,+ groupCount = count,+ memberIndex = 0,+ memberThreshold = 1,+ value = v+ }++-- | The indices of a mnemonic's words, which must all be in the list.+indicesOf :: Text -> [Int]+indicesOf = either error id . mnemonicToIndices++-- | The one mnemonic, or the first, of a vector by its number.+mnemonicOf :: [(Text, [Text], Text, Text)] -> Int -> Text+mnemonicOf vectors n = case vectors !! (n - 1) of+ (_, m : _, _, _) -> m+ (d, [], _, _) -> error ("vector without mnemonics: " <> T.unpack d)++-- | Why each invalid vector is refused, by its number.+refusalOf :: Int -> String+refusalOf n = case n of+ 2 -> checksum+ 3 -> padding+ 5 -> oneShareOfTwo 0+ 6 -> differ "identifier"+ 7 -> differ "iteration exponent"+ 8 -> differ "group threshold"+ 9 -> differ "group count"+ 10 -> thresholdAboveCount+ 11 -> duplicate+ 12 -> memberThresholds+ 13 -> digest+ 14 -> oneGroupOfTwo+ 15 -> oneGroupOfTwo+ 16 -> oneShareOfTwo 3+ 21 -> checksum+ 22 -> padding+ 24 -> oneShareOfTwo 0+ 25 -> differ "identifier"+ 26 -> differ "iteration exponent"+ 27 -> differ "group threshold"+ 28 -> differ "group count"+ 29 -> thresholdAboveCount+ 30 -> duplicate+ 31 -> memberThresholds+ 32 -> digest+ 33 -> oneGroupOfTwo+ 34 -> oneGroupOfTwo+ 35 -> oneShareOfTwo 3+ 39 -> "The mnemonic has 19 words, but a share has at least 20"+ 40 -> "The mnemonic has 21 words, which is not a valid share length"+ _ -> error ("vector " <> show n <> " is valid")+ where+ checksum = "The checksum of the mnemonic is invalid"+ padding = "The padding bits of the share value are not zero"+ differ what = "The shares do not have the same " <> what+ thresholdAboveCount = "The group threshold is greater than the group count"+ duplicate = "Group 0 has two shares with the member index 2"+ memberThresholds = "The shares of group 0 do not have the same member threshold"+ digest = "The digest of the secret does not match"+ oneGroupOfTwo = "The shares come from 1 group, but the group threshold is 2"+ oneShareOfTwo g = "Group " <> show (g :: Int) <> " has 1 share, but its member threshold is 2"++-- | Every set of mnemonics of a split case that meets its scheme: enough+-- groups, and enough members of each.+thresholdSubsets :: SplitCase -> [[Text]]+thresholdSubsets c =+ [ concat members+ | chosen <- subsequences (zip (map fst c.caseScheme.groups) c.caseMnemonics),+ length chosen == c.caseScheme.groupThreshold,+ members <- mapM (\(threshold, ms) -> [ms' | ms' <- subsequences ms, length ms' == threshold]) chosen+ ]++-- | A share of the given fields, decoded from its mnemonic for its value.+share :: Int -> Bool -> Int -> Int -> Int -> Int -> Int -> Int -> Text -> Share+share i ext e gi gt gc mi mt mnemonic =+ case decodeShare mnemonic of+ Right s+ | (s.identifier, s.extendable, s.iterationExponent, s.groupIndex, s.groupThreshold, s.groupCount, s.memberIndex, s.memberThreshold) == (i, ext, e, gi, gt, gc, mi, mt) -> s+ | otherwise -> error ("the mnemonic does not carry the fields given: " <> show s)+ Left err -> error err++bytesOf :: Int -> Gen ByteString+bytesOf n = B.pack <$> vectorOf n arbitrary++-- | A source of random bytes that gives zeros and records how many bytes+-- each request asked for, in order.+recordedZeros :: Int -> State [Int] ByteString+recordedZeros n = modify (++ [n]) >> return (B.replicate n 0)++-- | The last n elements of a list.+lastN :: Int -> [a] -> [a]+lastN n xs = drop (length xs - n) xs++-- | A scheme of one to three groups of one to four members, a secret of 16+-- to 64 bytes, a printable ASCII passphrase, and the shares of the secret+-- drawn from the generator.+splitOfAnyScheme :: Gen (Scheme, Bool, Text, ByteString, Either String [[Share]])+splitOfAnyScheme = do+ groupCount <- choose (1, 3)+ groups <- vectorOf groupCount $ do+ members <- choose (1, 4)+ -- a group with a member threshold of 1 has one member+ threshold <- if members == 1 then return 1 else choose (2, members)+ return (threshold, members)+ groupThreshold <- choose (1, groupCount)+ let scheme = Scheme groupThreshold groups+ ext <- arbitrary+ passphrase <- T.pack <$> listOf (elements [' ' .. '~'])+ secret <- choose (8, 32) >>= \half -> bytesOf (2 * half)+ drawn <- generateSharesFrom bytesOf scheme ext 0 passphrase secret+ return (scheme, ext, passphrase, secret, drawn)++-- | A position in the 20 words of the first vector after the two words+-- that hold the extendable flag, and a word of the list to put there.+changedWord :: Gen (Int, Text)+changedWord = (,) <$> choose (2, 19) <*> elements (V.toList slip39WordList)
test/Haskoin/Crypto/KeysSpec.hs view
@@ -5,17 +5,20 @@ module Haskoin.Crypto.KeysSpec (spec) where +import Control.Exception (evaluate) import Control.Lens import Control.Monad import Data.Aeson as A import Data.Aeson.Lens import Data.ByteString qualified as B import Data.ByteString.Char8 qualified as C+import Data.Char (isAsciiLower, isAsciiUpper, toLower, toUpper) import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Default (def) import Data.Either (isLeft)+import Data.List (isInfixOf) import Data.Maybe import Data.Serialize qualified as S import Data.String (fromString)@@ -25,9 +28,11 @@ import Haskoin.Address import Haskoin.Crypto import Haskoin.Network.Constants+import Haskoin.Network.Data (Network (..)) import Haskoin.Script import Haskoin.Util import Haskoin.Util.Arbitrary+import System.Timeout (timeout) import Test.HUnit import Test.Hspec import Test.Hspec.QuickCheck@@ -56,8 +61,12 @@ spec = prepareContext $ \ctx -> do describe "Key pair property checks" $ do testIdentity $ identityTests ctx- prop "Public key is canonical" $- forAll (arbitraryKeyPair ctx) (isCanonicalPubKey ctx . snd)+ prop "a compressed key is written in 33 bytes starting with 02 or 03, an uncompressed key in 65 bytes starting with 04" $+ forAll (arbitraryKeyPair ctx) $ \(_, pub) ->+ let bytes = marshal ctx pub+ in if pub.compress+ then B.length bytes == 33 && B.head bytes `elem` [0x02, 0x03]+ else B.length bytes == 65 && B.head bytes == 0x04 prop "Public key show . read identity" $ forAll (arbitraryKeyPair ctx) $ \(_, pub) -> (read . show) pub == pub@@ -78,7 +87,6 @@ it "Passes PubKey compression tests" $ testKeyCompressed ctx it "Passes address matching tests" $ testMatchingAddress ctx it "Passes signature verification" $ testSigs ctx- it "Passes deterministic signing tests" $ testDetSigning ctx describe "MiniKey vectors" $ it "Passes MiniKey decoding tests" testMiniKey describe "Secret key range" $ do@@ -121,6 +129,10 @@ fromWif btc "3QJmnh" `shouldBe` Nothing it "refuses a string longer than any WIF" $ fromWif btc (T.replicate 53 "z") `shouldBe` Nothing+ it "refuses a string of a million characters at once, without decoding it" $+ -- Decoding that much Base58 takes minutes. The length check comes first.+ timeout 1000000 (evaluate (fromWif btc (T.replicate 1000000 "z")))+ `shouldReturn` Just 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)@@ -133,8 +145,22 @@ 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+ it "decodes a 30-character mini key" $+ -- the sample key of the Bitcoin wiki+ fromMiniKey "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy"+ `shouldBe` Just (wrapSecKey False "4c7a9640c72dc2099f23715d0c8a0d8a35f8906e3cab61dd3f78b67bf887c9ab")+ it "refuses a mini key of 21 or 23 characters, even with a zero check byte" $ do+ -- SHA-256 of each string followed by "?" starts with 0x00+ fromMiniKey "S4b3N3oGqDqR5jNuxE1Be" `shouldBe` Nothing+ fromMiniKey "S4b3N3oGqDqR5jNuxEvD13Z" `shouldBe` Nothing+ it "refuses a string that does not start with S" $+ -- 22 Base58 characters with a zero check byte+ fromMiniKey "T4b3N3oGqDqR5jNuxEv13r" `shouldBe` Nothing+ it "refuses a character outside the Base58 alphabet" $ do+ -- 22 characters with a zero check byte: one holds the digit 0, the+ -- other the letters O, I and l and underscores+ fromMiniKey "S4b3N3oGqDqR5jNux0v1B7" `shouldBe` Nothing+ fromMiniKey "0OIl_______________607" `shouldBe` Nothing describe "Key tweaks" $ do it "adding a zero tweak leaves the secret key unchanged" $ tweakSecKey ctx secKey1 zeroHash `shouldBe` Just secKey1@@ -171,13 +197,34 @@ it "refuses a public key with an unknown prefix byte" $ (unmarshalJSON ctx "\"0579be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"" :: Maybe PublicKey) `shouldBe` Nothing+ it "refuses a hybrid public key" $+ -- 06, then x and the even y of the public key of 1. Consensus takes+ -- this form, the relay rules of Bitcoin Core do not, and this library+ -- reads keys in the two standard forms only.+ ( unmarshalJSON+ ctx+ "\"0679be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\+ \483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8\"" ::+ 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" $+ describe "Binary private key" $ do+ it "is the 32 bytes of the key, then 01 for a compressed public key or 00" $ do+ runPutS (serialize (wrapSecKey True secKey1))+ `shouldBe` hex "000000000000000000000000000000000000000000000000000000000000000101"+ runPutS (serialize (wrapSecKey False secKey1))+ `shouldBe` hex "000000000000000000000000000000000000000000000000000000000000000100"+ runGetS deserialize (hex "000000000000000000000000000000000000000000000000000000000000000101")+ `shouldBe` Right (wrapSecKey True secKey1) it "refuses a serialized zero key" $ (runGetS deserialize (B.replicate 33 0) :: Either String PrivateKey) `shouldSatisfy` isLeft+ it "refuses the curve order n as a serialized key" $+ (runGetS deserialize (hex "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036414101") :: Either String PrivateKey)+ `shouldSatisfy` either ("curve order" `isInfixOf`) (const False) describe "Showing a private key" $ do -- The secret of key 1 must not appear in the text it "does not reveal the secret" $@@ -186,14 +233,16 @@ 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" $- mapM_ (testKeyIOValidVector ctx) vectors- describe "key_io_invalid.json vectors" $ do- vectors <- runIO (readTestFile "key_io_invalid.json" :: IO [[Text]])- it "Passes the key_io_invalid.json vectors" $- mapM_ (testKeyIOInvalidVector ctx) vectors+ describe "key_io vectors of Bitcoin Core 32.1" $ do+ valid <- runIO (readTestFile "key_io_valid.json" :: IO [(Text, Text, A.Value)])+ invalid <- runIO (readTestFile "key_io_invalid.json" :: IO [[Text]])+ it "the files hold 70 valid and 70 invalid vectors" $ do+ length valid `shouldBe` 70+ length invalid `shouldBe` 70+ it "reads and writes every valid private key and address" $+ mapM_ (testKeyIOValidVector ctx) valid+ it "refuses every invalid string as an address and as a private key, on every network" $+ mapM_ testKeyIOInvalidVector invalid hex :: Text -> B.ByteString hex = fromJust . decodeHex@@ -214,25 +263,6 @@ orderMinusOneHash = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140" orderHash = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" --- github.com/bitcoin/bitcoin/blob/master/src/script.cpp--- from function IsCanonicalPubKey-isCanonicalPubKey :: Ctx -> PublicKey -> Bool-isCanonicalPubKey ctx p =- not $- -- Non-canonical public key: too short- (B.length bs < 33)- ||- -- Non-canonical public key: invalid length for uncompressed key- (B.index bs 0 == 4 && B.length bs /= 65)- ||- -- Non-canonical public key: invalid length for compressed key- (B.index bs 0 `elem` [2, 3] && B.length bs /= 33)- ||- -- Non-canonical public key: compressed nor uncompressed- (B.index bs 0 `notElem` [2, 3, 4])- where- bs = marshal ctx p- testMiniKey :: Assertion testMiniKey = assertEqual "fromMiniKey" (Just res) (go "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy")@@ -240,64 +270,84 @@ go = fmap (encodeHex . (.key.get)) . fromMiniKey res = "4c7a9640c72dc2099f23715d0c8a0d8a35f8906e3cab61dd3f78b67bf887c9ab" --- Test vectors from:--- https://github.com/bitcoin/bitcoin/blob/master/src/test/key_io_tests.cpp+-- Test vectors from src/test/data/key_io_valid.json and key_io_invalid.json of+-- Bitcoin Core 32.1. The checks are those of src/test/key_io_tests.cpp. +-- | One valid vector: a string, the bytes it stands for and its chain. For a+-- private key the bytes are the secret, for an address the output script. testKeyIOValidVector :: Ctx -> (Text, Text, A.Value) -> Assertion-testKeyIOValidVector ctx (a, payload, obj)- | disabled = return () -- There are invalid version 1 bech32 addresses- | isPrv = do- -- Test from WIF to SecKey- let isComp = obj ^?! key "isCompressed" . _Bool- prvKeyM = fromWif net a- prvKeyHexM = encodeHex . (.key.get) <$> prvKeyM- assertBool "Valid PrvKey" $ isJust prvKeyM- assertEqual "Valid compression" (Just isComp) ((.compress) <$> prvKeyM)- assertEqual "WIF matches payload" (Just payload) prvKeyHexM- let prvAsPubM :: Maybe ScriptOutput- prvAsPubM = (eitherToMaybe . unmarshal ctx <=< decodeHex) a- assertBool "PrvKey is invalid ScriptOutput" $ isNothing prvAsPubM- -- Test from SecKey to WIF- let secM = secKey =<< decodeHex payload- wifM = toWif net . wrapSecKey isComp <$> secM- assertEqual "Payload matches WIF" (Just a) wifM+testKeyIOValidVector ctx (string, payload, metadata)+ | isPrivkey = do+ let compressed = metadata ^?! key "isCompressed" . _Bool+ secret = fromMaybe (error "bad secret in the vector file") (secKey (hex payload))+ assertEqual+ ("the WIF " <> cs string <> " holds the key and its compression flag")+ (Just (payload, compressed))+ ((\k -> (encodeHex k.key.get, k.compress)) <$> fromWif net string)+ assertEqual+ ("the key is written as the WIF " <> cs string)+ string+ (toWif net (wrapSecKey compressed secret))+ assertEqual+ ("the WIF " <> cs string <> " is not an address")+ Nothing+ (textToAddr net string) | otherwise = do- -- Test Addr to Script- let addrM = textToAddr net a- outputM = addressToOutput =<< addrM- scriptM = encodeHex . marshal ctx <$> outputM- assertBool ("Valid Address " <> cs a) $ isJust addrM- assertEqual "Address matches payload" (Just payload) scriptM- let pubAsWifM = fromWif net a- pubAsSecM = secKey =<< decodeHex a- assertBool "Address is invalid Wif" $ isNothing pubAsWifM- assertBool "Address is invalid PrvKey" $ isNothing pubAsSecM- -- Test Script to Addr- let outM = eitherToMaybe . unmarshal ctx =<< decodeHex payload- resM = addrToText net =<< outputAddress ctx =<< outM- assertEqual "Payload matches address" (Just a) resM+ assertEqual+ ("the address " <> cs string <> " pays to the script")+ (Just payload)+ (encodeHex <$> (addressToScriptBS ctx =<< textToAddr net string))+ assertEqual+ ("the script is written as the address " <> cs string)+ (Just string)+ (addrToText net =<< eitherToMaybe (scriptToAddressBS net ctx (hex payload)))+ -- With the case of every letter flipped, a Bech32 address reads the+ -- same and a Base58 address does not read+ assertEqual+ ("the address " <> cs string <> " with its case flipped")+ (if tryCaseFlip then Just payload else Nothing)+ (encodeHex <$> (addressToScriptBS ctx =<< textToAddr net (flipCase string)))+ assertEqual+ ("the address " <> cs string <> " is not a private key")+ Nothing+ (fromWif net string) where- isPrv = obj ^?! key "isPrivkey" . _Bool- disabled = fromMaybe False $ obj ^? key "disabled" . _Bool- chain = obj ^?! key "chain" . _String+ isPrivkey = metadata ^?! key "isPrivkey" . _Bool+ tryCaseFlip = fromMaybe False (metadata ^? key "tryCaseFlip" . _Bool) net =- case chain of+ case metadata ^?! key "chain" . _String of "main" -> btc- "test" -> btcTest+ "testnet4" -> btcTest4 "regtest" -> btcRegTest- _ -> error "Invalid chain key in key_io_valid.json"+ -- This library has no signet. Signet writes keys and addresses as+ -- testnet3 does: the same version bytes and the prefix "tb"+ "signet" -> btcTest+ chain -> error ("Unknown chain in key_io_valid.json: " <> cs chain) -testKeyIOInvalidVector :: Ctx -> [Text] -> Assertion-testKeyIOInvalidVector ctx [a] = do- let wifMs = (`fromWif` a) <$> allNets- secKeyM = (secKey <=< decodeHex) a- scriptM :: Maybe ScriptOutput- scriptM = (eitherToMaybe . unmarshal ctx <=< decodeHex) a- assertBool "Payload is invalid WIF" $ all isNothing wifMs- assertBool "Payload is invalid SecKey" $ isNothing secKeyM- assertBool "Payload is invalid Script" $ isNothing scriptM-testKeyIOInvalidVector _ _ = assertFailure "Invalid test vector"+-- | Upper-case letters become lower-case and the reverse.+flipCase :: Text -> Text+flipCase = T.map flipLetter+ where+ flipLetter c+ | isAsciiLower c = toUpper c+ | isAsciiUpper c = toLower c+ | otherwise = c +-- | One invalid vector: a string that is no address and no private key, on+-- any network.+testKeyIOInvalidVector :: [Text] -> Assertion+testKeyIOInvalidVector [string] =+ forM_ [btc, btcTest, btcTest4, btcRegTest] $ \net -> do+ assertEqual+ (show string <> " is not an address on " <> net.name)+ Nothing+ (textToAddr net string)+ assertEqual+ (show string <> " is not a private key on " <> net.name)+ Nothing+ (fromWif net string)+testKeyIOInvalidVector _ = assertFailure "Invalid test vector"+ -- Test vectors from: -- https://github.com/bitcoin/bitcoin/blob/master/src/test/key_tests.cpp @@ -361,18 +411,6 @@ assertBool "Key 2C, Sign2" $ verifyHashSig ctx h sign2 (pub2C ctx).point assertBool "Key 2C, Sign1C" $ not $ verifyHashSig ctx h sign1C (pub2C ctx).point assertBool "Key 2C, Sign2C" $ verifyHashSig ctx h sign2C (pub2C ctx).point--testDetSigning :: Ctx -> Assertion-testDetSigning ctx = do- let m = doubleSHA256 ("Very deterministic message" :: B.ByteString)- assertEqual- "Det sig 1"- (signHash ctx sec1.key m)- (signHash ctx sec1C.key m)- assertEqual- "Det sig 2"- (signHash ctx sec2.key m)- (signHash ctx sec2C.key m) strSecret1, strSecret2, strSecret1C, strSecret2C :: Text strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"
test/Haskoin/Crypto/SignatureSpec.hs view
@@ -9,7 +9,9 @@ import Data.Bits (testBit) import Data.ByteString (ByteString) import Data.ByteString qualified as BS+import Data.Aeson.Types qualified as A (parseEither) import Data.Either (isLeft)+import Data.List (isInfixOf) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe@@ -77,9 +79,45 @@ 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" $+ -- BIP 66 is about the encoding only. Low S is a relay rule of Bitcoin+ -- Core (SCRIPT_VERIFY_LOW_S), not a consensus rule.+ it "accepts a signature whose s is n - 1, above half the curve order" $ do+ -- DER: 30 26 | 02 01 01 | 02 21 00 <n - 1>, 3 + 2 + 33 = 38 = 0x26 bytes+ let der = hex "3026020101022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"+ fmap (exportSig ctx) (decodeStrictSig ctx der) `shouldBe` Just der+ fmap (isCanonicalHalfOrder ctx) (decodeStrictSig ctx der) `shouldBe` Just False+ prop "accepts the high-S form of any signature" $ forAll (arbitrarySignature ctx) $ \(_, _, s) ->- decodeStrictSig ctx (exportSig ctx (highSVariant ctx s)) == Nothing+ decodeStrictSig ctx (exportSig ctx (highSVariant ctx s)) == Just (highSVariant ctx s)+ it "accepts an r that needs a leading zero byte" $+ -- r = 0x80 is written 00 80, since 80 alone would be negative+ fmap (exportSig ctx) (decodeStrictSig ctx (hex "300702020080020101"))+ `shouldBe` Just (hex "300702020080020101")+ it "refuses a negative r or s" $ do+ decodeStrictSig ctx (hex "3006020181020101") `shouldBe` Nothing+ decodeStrictSig ctx (hex "3006020101020181") `shouldBe` Nothing+ it "refuses an r or s with a leading zero byte that is not needed" $ do+ decodeStrictSig ctx (hex "300702020001020101") `shouldBe` Nothing+ decodeStrictSig ctx (hex "300702010102020001") `shouldBe` Nothing+ it "refuses an r or s of no bytes" $ do+ decodeStrictSig ctx (hex "30050200020101") `shouldBe` Nothing+ decodeStrictSig ctx (hex "30050201010200") `shouldBe` Nothing+ it "refuses an r or s that is not tagged as an integer" $ do+ decodeStrictSig ctx (hex "3006030101020101") `shouldBe` Nothing+ decodeStrictSig ctx (hex "3006020101030101") `shouldBe` Nothing+ it "refuses a sequence whose length is not that of its content" $ do+ decodeStrictSig ctx (hex "3005020101020101") `shouldBe` Nothing+ decodeStrictSig ctx (hex "3007020101020101") `shouldBe` Nothing+ it "refuses a byte after the sequence" $+ decodeStrictSig ctx (hex "300602010102010100") `shouldBe` Nothing+ it "refuses a length written in the long form" $+ decodeStrictSig ctx (hex "308106020101020101") `shouldBe` Nothing+ it "refuses no bytes at all" $+ decodeStrictSig ctx "" `shouldBe` Nothing+ it "refuses an r at the curve order, which BIP 66 allows but which can never verify" $+ -- DER: 30 26 | 02 21 00 <n> | 02 01 01+ decodeStrictSig ctx (hex "3026022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101")+ `shouldBe` Nothing describe "Binary signature decoding" $ do it "decodes the smallest signature" $ fmap (exportSig ctx) (unmarshal ctx (hex "3006020101020101"))@@ -92,13 +130,24 @@ (unmarshal ctx (hex "308106020101020101") :: Either String Sig) `shouldSatisfy` isLeft it "refuses a signature cut short" $ (unmarshal ctx (hex "3006020101") :: Either String Sig) `shouldSatisfy` isLeft+ it "refuses bytes after the signature" $+ (unmarshal ctx (hex "300602010102010100ffff") :: Either String Sig)+ `shouldSatisfy` either ("bytes left over" `isInfixOf`) (const False)+ it "decodes a signature whose s is n - 1" $+ fmap (exportSig ctx) (unmarshal ctx (hex "3026020101022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"))+ `shouldBe` Right (hex "3026020101022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140") describe "Signature JSON" $ do- prop "refuses a high-S signature, like the binary form" $+ prop "reads 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+ == Just (highSVariant ctx s)+ it "refuses a signature whose r is zero, like the binary form" $+ (unmarshalJSON ctx "\"3006020100020101\"" :: Maybe Sig) `shouldBe` Nothing it "refuses a signature that is not hex" $ (unmarshalJSON ctx "\"zz\"" :: Maybe Sig) `shouldBe` Nothing+ it "does not copy the text it refuses into its error message" $+ (A.parseEither (unmarshalValue ctx) (A.String "zz-attacker-text") :: Either String Sig)+ `shouldBe` Left "Error in $: Could not decode signature" describe "High-S signatures" $ do prop "are not in canonical half order" $ forAll (arbitrarySignature ctx) $ \(_, _, s) ->@@ -419,7 +468,7 @@ rdm = PayMulSig (toPubKey ctx <$> keys) 2 sigIn = SigInput- (toP2WSH $ either error id $ encodeOutput ctx rdm)+ (either error id $ toP2WSH =<< encodeOutput ctx rdm) 100000000 op0 sigHashAll@@ -510,7 +559,7 @@ ] Just keys = traverse secHexKey rawKeys rdm = PayMulSig (toPubKey ctx <$> keys) 6- sigIn sh = SigInput (toP2WSH $ either error id $ encodeOutput ctx rdm) 987654321 op0 sh (Just rdm)+ sigIn sh = SigInput (either error id $ toP2WSH =<< encodeOutput ctx rdm) 987654321 op0 sh (Just rdm) sigHashesA = [sigHashAll, sigHashNone, sigHashSingle] sigHashesB = setAnyoneCanPay <$> sigHashesA sigIns = sigIn <$> (sigHashesA <> sigHashesB)
test/Haskoin/NetworkSpec.hs view
@@ -350,6 +350,12 @@ compactBlockSpec blockFilterSpec constantsSpec+ sizeLimitSpec+ itemLimitSpec+ compactPositionSpec+ witnessFormatSpec+ bloomMatchSpec ctx+ boundarySpec -- VarInt ---------------------------------------------------------------------- @@ -402,6 +408,17 @@ 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 "writes the count of a list of 253 elements in three bytes" $+ -- 253 = 0xfd is the first count that does not fit one byte+ B.take 4 (runPutS (putVarIntList putWord8 (replicate 253 7))) `shouldBe` hex "fdfd0007"+ it "reads a list of 253 elements" $+ fmap length (runGetS (getVarIntList getWord8) (hex "fdfd00" <> B.replicate 253 7)) `shouldBe` Right 253+ it "refuses a list above a given limit as soon as the count is read" $ do+ runGetS (getVarIntListMax "test" 2 getWord8) (hex "03") `failsWith` "test: too many entries: 3, the limit is 2"+ runGetS (getVarIntListMax "test" 2 getWord8) (hex "020102") `shouldBe` Right [1, 2]+ it "knows the item limits of the protocol" $+ -- MAX_INV_SZ, MAX_ADDR_TO_SEND, MAX_HEADERS_RESULTS, MAX_LOCATOR_SZ, and BIP 157+ [maxInvSize, maxAddrSize, maxHeadersSize, maxLocatorSize, maxFilterHashes] `shouldBe` [50000, 1000, 2000, 101, 2000] it "refuses a list count above the limit" $ runGetS (getVarIntList getWord8) (hex "fe01000002") `failsWith` "List count too large" @@ -795,16 +812,22 @@ parse (hex "81") `shouldBe` Right BloomUpdateAll parse (hex "82") `shouldBe` Right BloomUpdateP2PubKeyOnly it "accepts the largest filter a peer may load" $+ -- 36000 bytes and 50 hash functions. Only sizes are compared: showing+ -- two filters of this size in a failure report needs gigabytes. let f = BloomFilter (S.replicate 36000 0x55) 50 0 BloomUpdateAll- in parse (runPutS (serialize f)) `shouldBe` Right f+ result = parse (runPutS (serialize f)) :: Either String BloomFilter+ in do+ fmap (\g -> (S.length g.array, g.functions, g.tweak, g.flags)) result+ `shouldBe` Right (36000, 50, 0, BloomUpdateAll)+ fmap (== f) result `shouldBe` Right True 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+ it "refuses a filter that announces 2^64 - 1 bytes" $+ -- the count is above the 36000 bytes a filter may have (parse (hex ("ffffffffffffffffff" <> "01000000" <> "00000000" <> "01")) :: Either String BloomFilter) `failsWith` "too large" it "writes filterload as the filter" $@@ -1106,9 +1129,10 @@ btc.bip34Hash `shouldBe` Just "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8" btc.bip65Height `shouldBe` 388381 btc.bip66Height `shouldBe` 363725- it "pins block 1 of testnet4" $ do+ it "pins no BIP34 block on testnet4, as Bitcoin Core does" $ do+ -- kernel/chainparams.cpp, CTestNet4Params: BIP34Height = 1, BIP34Hash = uint256{} btcTest4.bip34Height `shouldBe` 1- btcTest4.bip34Hash `shouldBe` Just "0000000012982b6d5f621229286b880e909984df669c2afabb102ce311b13f28"+ btcTest4.bip34Hash `shouldBe` Nothing 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]@@ -1124,3 +1148,362 @@ nodeCompactFilters `shouldBe` 64 nodeNetworkLimited `shouldBe` 1024 nodeP2PV2 `shouldBe` 2048++-- Message sizes per command -----------------------------------------------------++-- | A P2P message as it is on the wire: magic, command padded with NUL+-- bytes, payload length, checksum and payload.+frame :: Network -> ByteString -> ByteString -> ByteString+frame net cmd payload =+ runPutS $ do+ putWord32be net.magic+ putByteString (cmd <> B.replicate (12 - B.length cmd) 0)+ putWord32le (fromIntegral (B.length payload))+ serialize (checkSum32 payload)+ putByteString payload++-- | A message header that announces a payload of the given length, with no+-- payload after it. Reading it fails either on the length or, when the+-- length is allowed, on the missing payload.+headerOnly :: Network -> ByteString -> Word32 -> ByteString+headerOnly net cmd len =+ runPutS $ do+ putWord32be net.magic+ putByteString (cmd <> B.replicate (12 - B.length cmd) 0)+ putWord32le len+ putWord32be 0++sizeLimitSpec :: Spec+sizeLimitSpec = describe "message size per command" $ do+ -- Bitcoin Cash Node, CMessageHeader::IsOversized: a message above+ -- MAX_PROTOCOL_MESSAGE_LENGTH (2 * 1024 * 1024 = 2097152) is oversized+ -- unless it is block, cmpctblock or blocktxn. Those three may be twice the+ -- block size: 2 * 32000000 = 64000000.+ it "refuses an inv of 2097153 bytes on bitcoin cash before reading the payload" $+ runGetS (getMessage bch) (headerOnly bch "inv" 2097153) `failsWith` "larger than the limit"+ it "accepts the length of an inv of 2097152 bytes on bitcoin cash" $+ runGetS (getMessage bch) (headerOnly bch "inv" 2097152) `failsWith` "too few bytes"+ it "refuses a getblocktxn, a merkleblock and a tx of 2097153 bytes on bitcoin cash" $+ forM_ ["getblocktxn", "merkleblock", "tx", "headers", "addr", "foo"] $ \cmd ->+ runGetS (getMessage bch) (headerOnly bch cmd 2097153) `failsWith` "larger than the limit"+ it "accepts the length of a block, a cmpctblock and a blocktxn of 64000000 bytes on bitcoin cash" $+ forM_ ["block", "cmpctblock", "blocktxn"] $ \cmd ->+ runGetS (getMessage bch) (headerOnly bch cmd 64000000) `failsWith` "too few bytes"+ it "refuses a block of 64000001 bytes on bitcoin cash" $+ forM_ ["block", "cmpctblock", "blocktxn"] $ \cmd ->+ runGetS (getMessage bch) (headerOnly bch cmd 64000001) `failsWith` "larger than the limit"+ it "accepts the length of any message of 4000000 bytes on bitcoin" $+ forM_ ["inv", "block", "tx", "foo"] $ \cmd ->+ runGetS (getMessage btc) (headerOnly btc cmd 4000000) `failsWith` "too few bytes"+ it "refuses any message of 4000001 bytes on bitcoin" $+ forM_ ["inv", "block", "tx", "foo"] $ \cmd ->+ runGetS (getMessage btc) (headerOnly btc cmd 4000001) `failsWith` "larger than the limit"+ it "reads a message whose payload is exactly the limit" $+ -- an unknown command with 4000000 payload bytes on bitcoin+ let result = runGetS (getMessage btc) (frame btc "foo" (B.replicate 4000000 0x61))+ in fmap (\m -> case m of MOther c p -> (c, B.length p); _ -> ("", 0)) result+ `shouldBe` Right ("foo", 4000000)+ it "gives the limit of each command on bitcoin cash" $ do+ map (maxPayloadSize bch) [MCBlock, MCCmpctBlock, MCBlockTxn] `shouldBe` [64000000, 64000000, 64000000]+ map (maxPayloadSize bch) [MCInv, MCTx, MCMerkleBlock, MCGetBlockTxn, MCHeaders, MCOther "foo"]+ `shouldBe` replicate 6 2097152+ it "gives 4000000 for every command on bitcoin" $+ map (maxPayloadSize btc) [MCBlock, MCCmpctBlock, MCBlockTxn, MCInv, MCTx, MCOther "foo"]+ `shouldBe` replicate 6 4000000+ it "gives twice the 2 MB block size for a block on bitcoin cash testnet4" $ do+ maxPayloadSize bchTest4 MCBlock `shouldBe` 4000000+ maxPayloadSize bchTest4 MCInv `shouldBe` 2097152+ it "limits messages without block content to 4 MB on bitcoin and 2 MiB on bitcoin cash" $+ -- MAX_PROTOCOL_MESSAGE_LENGTH: 4 * 1000 * 1000 in Bitcoin Core, 2 * 1024 * 1024 in Bitcoin Cash Node+ map (.maxProtocolMessageSize) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+ `shouldBe` [4000000, 4000000, 4000000, 4000000, 2097152, 2097152, 2097152, 2097152]+ it "checks the magic before the command name" $+ -- magic 00000000 and the command "ping" followed by a line feed+ runGetS (getMessage btc) (hex ("00000000" <> "70696e670a00000000000000" <> "00000000" <> "5df6e0e2"))+ `failsWith` "Invalid network magic"++-- Item counts ---------------------------------------------------------------------++itemLimitSpec :: Spec+itemLimitSpec = describe "item counts" $ do+ -- Bitcoin Core net_processing.cpp and Bitcoin Cash Node net.h:+ -- MAX_INV_SZ = 50000, MAX_ADDR_TO_SEND = 1000, MAX_HEADERS_RESULTS = 2000,+ -- MAX_LOCATOR_SZ = 101. BIP 157: at most 2000 filter hashes.+ let invEntry = hex ("01000000" <> h1Hex)+ addrEntry = hex ("e215104d" <> "0100000000000000" <> ipv4Bytes)+ addrV2Entry = hex ("e215104d" <> "01" <> "01" <> "04" <> "0a000001" <> "208d")+ headerEntry = hex (genesisHeaderHex <> "00")+ -- counts as three-byte compact sizes, little-endian+ -- 50000 = 0xc350, 50001 = 0xc351, 1000 = 0x03e8, 1001 = 0x03e9,+ -- 2000 = 0x07d0, 2001 = 0x07d1+ many :: Text -> Int -> ByteString -> ByteString+ many count n entry = hex count <> B.concat (replicate n entry)+ it "accepts an inv of 50000 entries" $+ fmap (\(Inv xs) -> length xs) (parse (many "fd50c3" 50000 invEntry)) `shouldBe` Right 50000+ it "refuses an inv of 50001 entries" $+ fmap (\(Inv xs) -> length xs) (parse (many "fd51c3" 50001 invEntry)) `failsWith` "too many"+ it "accepts a getdata of 50000 entries and refuses 50001" $ do+ fmap (\(GetData xs) -> length xs) (parse (many "fd50c3" 50000 invEntry)) `shouldBe` Right 50000+ fmap (\(GetData xs) -> length xs) (parse (many "fd51c3" 50001 invEntry)) `failsWith` "too many"+ it "accepts an addr of 1000 entries and refuses 1001" $ do+ fmap (\(Addr xs) -> length xs) (parse (many "fde803" 1000 addrEntry)) `shouldBe` Right 1000+ fmap (\(Addr xs) -> length xs) (parse (many "fde903" 1001 addrEntry)) `failsWith` "too many"+ it "accepts an addrv2 of 1000 entries and refuses 1001" $ do+ fmap (\(AddrV2 xs) -> length xs) (parse (many "fde803" 1000 addrV2Entry)) `shouldBe` Right 1000+ fmap (\(AddrV2 xs) -> length xs) (parse (many "fde903" 1001 addrV2Entry)) `failsWith` "too many"+ it "accepts a headers message of 2000 entries and refuses 2001" $ do+ fmap (\(Headers xs) -> length xs) (parse (many "fdd007" 2000 headerEntry)) `shouldBe` Right 2000+ fmap (\(Headers xs) -> length xs) (parse (many "fdd107" 2001 headerEntry)) `failsWith` "too many"+ it "accepts a getheaders locator of 101 hashes and refuses 102" $ do+ -- version 70001 = 0x00011171, then the locator, then the stop hash+ fmap (\g -> length g.locator) (parse (hex "71110100" <> many "65" 101 (hex h1Hex) <> hex h2Hex) :: Either String GetHeaders)+ `shouldBe` Right 101+ fmap (\g -> length g.locator) (parse (hex "71110100" <> many "66" 102 (hex h1Hex) <> hex h2Hex) :: Either String GetHeaders)+ `failsWith` "too many"+ it "accepts a getblocks locator of 101 hashes and refuses 102" $ do+ fmap (\g -> length g.locator) (parse (hex "71110100" <> many "65" 101 (hex h1Hex) <> hex h2Hex) :: Either String GetBlocks)+ `shouldBe` Right 101+ fmap (\g -> length g.locator) (parse (hex "71110100" <> many "66" 102 (hex h1Hex) <> hex h2Hex) :: Either String GetBlocks)+ `failsWith` "too many"+ it "accepts a cfheaders of 2000 filter hashes and refuses 2001" $ do+ fmap (\c -> length c.filterHashes) (parse (hex ("00" <> bh1Hex <> h1Hex) <> many "fdd007" 2000 (hex h2Hex)) :: Either String CFHeaders)+ `shouldBe` Right 2000+ fmap (\c -> length c.filterHashes) (parse (hex ("00" <> bh1Hex <> h1Hex) <> many "fdd107" 2001 (hex h2Hex)) :: Either String CFHeaders)+ `failsWith` "too many"++-- Compact block positions per network -----------------------------------------------++compactPositionSpec :: Spec+compactPositionSpec = describe "compact block positions" $ do+ -- BIP 152 and Bitcoin Core (blockencodings.h) use 16-bit positions:+ -- BlockTransactionsRequest::indexes is std::vector<uint16_t>, and a+ -- cmpctblock with more than 65535 transactions fails with "indexes+ -- overflowed 16 bits". Bitcoin Cash Node uses uint32_t in both places.+ let requestFrame positions = frame btc "getblocktxn" (hex (bh1Hex <> positions))+ indexes = fmap (\m -> case m of MGetBlockTxn g -> g.indexes; _ -> [])+ tx = hex oneInputTxHex+ compact n prefilled =+ hex (genesisHeaderHex <> "0000000000000000")+ <> runPutS (putVarInt (n :: Int))+ <> B.concat (replicate n (hex "010000000000"))+ <> prefilled+ counts = fmap (\m -> case m of MCompactBlock c -> (length c.shortIds, map (.index) c.prefilled); _ -> (0, []))+ it "reads the position 65535 in a getblocktxn on bitcoin" $+ indexes (runGetS (getMessage btc) (requestFrame "01fdffff")) `shouldBe` Right [65535]+ it "refuses the position 65536 in a getblocktxn on bitcoin" $+ indexes (runGetS (getMessage btc) (requestFrame "01fe00000100")) `failsWith` "16 bits"+ it "refuses a getblocktxn position reached by adding distances beyond 65535 on bitcoin" $+ -- 65535, then a distance of 0, which is position 65536+ indexes (runGetS (getMessage btc) (requestFrame "02fdffff00")) `failsWith` "16 bits"+ it "reads the position 65536 in a getblocktxn on bitcoin cash" $+ indexes (runGetS (getMessage bch) (frame bch "getblocktxn" (hex (bh1Hex <> "01fe00000100"))))+ `shouldBe` Right [65536]+ it "refuses a prefilled transaction at position 65536 on bitcoin" $+ counts (runGetS (getMessage btc) (frame btc "cmpctblock" (compact 0 (hex "01fe00000100" <> tx))))+ `failsWith` "16 bits"+ it "reads a compact block of 65535 transactions on bitcoin" $+ -- 65534 short ids and one prefilled transaction+ counts (runGetS (getMessage btc) (frame btc "cmpctblock" (compact 65534 (hex "0100" <> tx))))+ `shouldBe` Right (65534, [0])+ it "refuses a compact block of 65536 transactions on bitcoin" $+ -- 65535 short ids and one prefilled transaction+ counts (runGetS (getMessage btc) (frame btc "cmpctblock" (compact 65535 (hex "0100" <> tx))))+ `failsWith` "16 bits"+ it "reads a compact block of 65536 transactions on bitcoin cash" $+ counts (runGetS (getMessage bch) (frame bch "cmpctblock" (compact 65535 (hex "0100" <> tx))))+ `shouldBe` Right (65535, [0])+ it "allows positions of 16 bits on bitcoin and of 32 bits on bitcoin cash" $+ map (.maxCompactBlockIndex) [btc, btcTest, btcTest4, btcRegTest, bch, bchTest, bchTest4, bchRegTest]+ `shouldBe` [65535, 65535, 65535, 65535, 4294967295, 4294967295, 4294967295, 4294967295]+ it "keeps 32-bit positions in the form without a network" $ do+ parse (hex (bh1Hex <> "01feffffffff")) `shouldBe` Right (GetBlockTxn bh1 [4294967295])+ hexOf (GetBlockTxn bh1 [4294967295]) `shouldBe` bh1Hex <> "01feffffffff"+ it "refuses a getblocktxn count above the list limit" $+ -- 0x02000001 positions announced+ (parse (hex (bh1Hex <> "fe01000002")) :: Either String GetBlockTxn) `failsWith` "List count too large"+ it "reads any non-zero announce byte of sendcmpct as true" $+ parse (hex ("02" <> "0100000000000000")) `shouldBe` Right (SendCompact True 1)++-- SegWit format on networks without SegWit -------------------------------------------++witnessFormatSpec :: Spec+witnessFormatSpec = describe "transaction format per network" $ do+ -- A transaction in the extended format of BIP 144: version, marker 00,+ -- flag 01, one input, one output, one witness stack with the item ab, lock+ -- time. The outpoint hash is 32 bytes of 0x11.+ let witnessTx =+ hex $+ "01000000"+ <> "0001"+ <> "01"+ <> h1Hex+ <> "00000000"+ <> "00"+ <> "ffffffff"+ <> "01"+ <> "e803000000000000"+ <> "0151"+ <> "0101ab"+ <> "00000000"+ shape = fmap (\m -> case m of MTx t -> (length t.inputs, length t.outputs, t.witness); _ -> (-1, -1, []))+ it "reads the witness of a tx message on bitcoin" $+ shape (runGetS (getMessage btc) (frame btc "tx" witnessTx)) `shouldBe` Right (1, 1, [[hex "ab"]])+ it "does not read the extended format in a tx message on bitcoin cash" $+ -- Bitcoin Cash Node has no witness format. It reads the marker as an+ -- input count of zero and the flag as an output count of one.+ shape (runGetS (getMessage bch) (frame bch "tx" witnessTx)) `shouldBe` Right (0, 1, [])+ it "does not read the extended format in a block message on bitcoin cash" $+ let block = hex genesisHeaderHex <> hex "01" <> witnessTx+ txShape = fmap (\m -> case m of MBlock b -> map (\t -> (length t.inputs, t.witness)) b.txs; _ -> [])+ in txShape (runGetS (getMessage bch) (frame bch "block" block)) `shouldBe` Right [(0, [])]+ it "reads a transaction in the old format only on a network without SegWit" $ do+ fmap (\t -> (length t.inputs, t.witness)) (runGetS (getNetworkTx btc) witnessTx) `shouldBe` Right (1, [[hex "ab"]])+ fmap (\t -> (length t.inputs, t.witness)) (runGetS (getNetworkTx bch) witnessTx) `shouldBe` Right (0, [])+ it "does not read the extended format in a blocktxn or a cmpctblock message on bitcoin cash" $ do+ let blockTxn = hex bh1Hex <> hex "01" <> witnessTx+ compact = hex (genesisHeaderHex <> "0000000000000000" <> "00" <> "0100") <> witnessTx+ fmap (\m -> case m of MBlockTxn b -> map (\t -> length t.inputs) b.transactions; _ -> [])+ (runGetS (getMessage bch) (frame bch "blocktxn" blockTxn))+ `shouldBe` Right [0]+ fmap (\m -> case m of MCompactBlock c -> map (\x -> length x.tx.inputs) c.prefilled; _ -> [])+ (runGetS (getMessage bch) (frame bch "cmpctblock" compact))+ `shouldBe` Right [0]+ it "reads an empty witness item as an empty byte string" $+ -- one input with a witness stack of three empty items+ let tx3 =+ hex $+ "01000000" <> "0001" <> "01" <> h1Hex <> "00000000" <> "00" <> "ffffffff"+ <> "01" <> "e803000000000000" <> "0151" <> "03000000" <> "00000000"+ in fmap (.witness) (parse tx3 :: Either String Tx) `shouldBe` Right [["", "", ""]]++-- Bloom filter matching -----------------------------------------------------------++-- | The transaction of the bloom_match case of Bitcoin Core+-- (src/test/bloom_tests.cpp),+-- b4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b.+bloomMatchTx :: Tx+bloomMatchTx =+ either error id . parse . hex $+ "01000000010b26e9b7735eb6aabdf358bab62f9816a21ba9ebdb719d5299e88607d722c190000000008b4830450220070aca44506c5cef3a16ed519d7c3c39f8aab192c4e1c90d065f37b8a4af6141022100a8e160b856c2d43d27d8fba71e5aef6405b8643ac4cb7cb3c462aced7f14711a0141046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c339ffffffff021bff3d11000000001976a91404943fdd508053c75000106d3bc6e2754dbcff1988ac2f15de00000000001976a914a266436d2965547608b9e15d9032a7b9d64fa43188ac00000000"++-- | The transaction that spends output 0 of 'bloomMatchTx' in the same case,+-- e2769b09e784f32f62ef849763d4f45b98e07ba658647343b915ff832b110436.+bloomSpendingTx :: Tx+bloomSpendingTx =+ either error id . parse . hex $+ "01000000016bff7fcd4f8565ef406dd5d63d4ff94f318fe82027fd4dc451b04474019f74b4000000008c493046022100da0dc6aecefe1e06efdf05773757deb168820930e3b0d03f46f5fcf150bf990c022100d25b5c87040076e4f253f8262e763e2dd51e7ff0be157727c4bc42807f17bd39014104e6c26ef67dc610d2cd192484789a6cf9aea9930b944b7e2db5342b9d9e5b9ff79aff9a2ee1978dd7fd01dfc522ee02283d3b06a9d03acf8096968d7dbb0f9178ffffffff028ba7940e000000001976a914badeecfdef0507247fc8f74241d73bc039972d7b88ac4094a802000000001976a914c10932483fec93ed51f5fe95e72559f2cc7043f988ac00000000"++bloomMatchSpec :: Ctx -> Spec+bloomMatchSpec ctx = describe "bloom filter matching, the bloom_match case of Bitcoin Core" $ do+ let fresh = bloomCreate 10 0.000001 0 BloomUpdateAll+ matches element = bloomRelevantUpdate ctx (bloomInsert fresh element) bloomMatchTx /= Nothing+ -- the outpoint 90c122d7...260b:n that the transaction spends+ spent n = hex "0b26e9b7735eb6aabdf358bab62f9816a21ba9ebdb719d5299e88607d722c190" <> hex n+ it "matches the transaction hash" $+ -- the hash in internal byte order+ matches (hex "6bff7fcd4f8565ef406dd5d63d4ff94f318fe82027fd4dc451b04474019f74b4") `shouldBe` True+ it "matches the signature of an input" $+ matches (hex "30450220070aca44506c5cef3a16ed519d7c3c39f8aab192c4e1c90d065f37b8a4af6141022100a8e160b856c2d43d27d8fba71e5aef6405b8643ac4cb7cb3c462aced7f14711a01") `shouldBe` True+ it "matches the public key of an input" $+ matches (hex "046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c339") `shouldBe` True+ it "matches the address of each output" $ do+ matches (hex "04943fdd508053c75000106d3bc6e2754dbcff19") `shouldBe` True+ matches (hex "a266436d2965547608b9e15d9032a7b9d64fa431") `shouldBe` True+ it "matches the outpoint that an input spends" $+ matches (spent "00000000") `shouldBe` True+ it "does not match another transaction hash" $+ matches (hex "36041132b83ff15b9143736458a67be0985bf4d4639784ef622ff384e7090000") `shouldBe` False+ it "does not match another address" $+ matches (hex "0000006d2965547608b9e15d9032a7b9d64fa431") `shouldBe` False+ it "does not match another output of the spent transaction" $+ matches (spent "01000000") `shouldBe` False+ it "matches the transaction that spends a matched output" $+ -- "Simple Bloom filter didn't add output" in Bitcoin Core+ let updated = bloomRelevantUpdate ctx (bloomInsert fresh (hex "04943fdd508053c75000106d3bc6e2754dbcff19")) bloomMatchTx+ in fmap (\f -> bloomRelevantUpdate ctx f bloomSpendingTx /= Nothing) updated `shouldBe` Just True+ it "leaves the filter as it is when only the transaction hash matches" $+ let f = bloomInsert fresh (hex "6bff7fcd4f8565ef406dd5d63d4ff94f318fe82027fd4dc451b04474019f74b4")+ in bloomRelevantUpdate ctx f bloomMatchTx `shouldBe` Just f+ describe "pay-to-pubkey test of BloomUpdateP2PubKeyOnly, as Solver in Bitcoin Core" $ do+ -- Solver checks the size and the first byte of a key, not the curve.+ let hybridKey = hex "06" <> B.replicate 64 0x11+ offCurveKey = hex "02" <> B.replicate 32 0xff+ g = hex "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"+ inserted key script =+ let bf = bloomInsert (bloomCreate 10 0.000001 0 BloomUpdateP2PubKeyOnly) key+ tx = txPaying [script]+ in fmap (`bloomContains` outpointBytes tx 0) (bloomRelevantUpdate ctx bf tx)+ it "inserts the outpoint of a pay-to-pubkey output with a hybrid key" $+ inserted hybridKey (hex "41" <> hybridKey <> hex "ac") `shouldBe` Just True+ it "inserts the outpoint of a pay-to-pubkey output whose key is not on the curve" $+ inserted offCurveKey (hex "21" <> offCurveKey <> hex "ac") `shouldBe` Just True+ it "inserts the outpoint of a bare multisig output with a key that is not on the curve" $+ inserted g (hex "51" <> hex "21" <> g <> hex "21" <> offCurveKey <> hex "52ae") `shouldBe` Just True+ it "inserts the outpoint of a bare 1-of-2 multisig output" $+ let k2 = hex "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"+ in inserted g (hex "51" <> hex "21" <> g <> hex "21" <> k2 <> hex "52ae") `shouldBe` Just True+ it "does not insert the outpoint of a pay-to-pubkey-hash output" $+ inserted p2pkhHash p2pkhScript `shouldBe` Just False+ it "does not insert the outpoint of a script that only starts like pay-to-pubkey" $+ -- a 33-byte push and OP_CHECKSIG followed by OP_NOP+ inserted g (hex "21" <> g <> hex "ac61") `shouldBe` Just False+ it "reads the flags byte 05 as update-all, since only the two low bits count" $+ (parse (hex "05") :: Either String BloomFlags) `shouldBe` Right BloomUpdateAll+ it "picks the number of hash functions with the integer division of Bitcoin Core" $+ -- 5 elements at a rate of 0.08: -1 / ln(2)^2 * 5 * ln(0.08) = 26.2 bits,+ -- 3 bytes. Then 3 * 8 / 5 = 4 in integers, and 4 * ln(2) = 2.77, so 2.+ let f = bloomCreate 5 0.08 0 BloomUpdateNone+ in (S.length f.array, f.functions) `shouldBe` (3, 2)++-- Boundaries --------------------------------------------------------------------------++boundarySpec :: Spec+boundarySpec = describe "boundaries" $ do+ it "refuses the largest value that a three-byte encoding does not need" $+ (parse (hex "fdfc00") :: Either String VarInt) `failsWith` "non-canonical"+ it "refuses the largest value that a five-byte encoding does not need" $+ (parse (hex "feffff0000") :: Either String VarInt) `failsWith` "non-canonical"+ it "refuses the largest value that a nine-byte encoding does not need" $+ (parse (hex "ffffffffff00000000") :: Either String VarInt) `failsWith` "non-canonical"+ it "accepts a list count equal to the limit and then fails on the missing elements" $+ -- 0x02000000 elements announced, none present+ runGetS (getVarIntList getWord8) (hex "fe00000002") `failsWith` "too few bytes"+ it "accepts a byte count equal to the limit and then fails on the missing bytes" $+ (parse (hex "fe00000002") :: Either String VarString) `failsWith` "too few bytes"+ it "accepts a space and a tilde in a command name" $ do+ -- 0x20 and 0x7e are the first and the last printable ASCII characters+ parse (hex "612062000000000000000000") `shouldBe` Right (MCOther "a b")+ parse (hex "617e62000000000000000000") `shouldBe` Right (MCOther "a~b")+ it "refuses the bytes 0x1f and 0x7f in a command name" $ do+ (parse (hex "611f62000000000000000000") :: Either String MessageCommand) `failsWith` "printable ASCII"+ (parse (hex "617f62000000000000000000") :: Either String MessageCommand) `failsWith` "printable ASCII"+ it "reads a version message with negative fields as negative numbers" $+ -- Bitcoin Core reads int nVersion, int64_t nTime and int starting_height+ let payload =+ hex "ffffffff" <> hex "0100000000000000" <> hex "ffffffffffffffff"+ <> B.drop 20 (hex (T.take 160 versionPayloadHex))+ <> hex "00"+ <> hex "ffffffff"+ in fmap (\v -> (toInteger v.version, toInteger v.timestamp, toInteger v.startHeight)) (parse payload :: Either String Version)+ `shouldBe` Right (-1, -1, -1)+ it "reads back the shown form of a command name with a byte above 0x7f" $+ read (show (MCOther "\200")) `shouldBe` MCOther "\200"+ it "reads back the shown form of a reject that names such a command" $+ -- command of one byte c8, code 0x10, empty reason+ let r = either error id (parse (hex "01c8" <> hex "10" <> hex "00")) :: Reject+ in read (show r) `shouldBe` r+ describe "addrv2" $ do+ it "keeps a Tor v2 entry whatever its length, since BIP 155 says to ignore it" $+ parse (hex ("00000000" <> "00" <> "03" <> "05" <> "0102030405" <> "0000"))+ `shouldBe` Right (NetworkAddressV2 0 0 AddrTorV2 (hex "0102030405") 0)+ it "reads the entries after a Tor v2 entry of the wrong length" $+ fmap (\(AddrV2 xs) -> map (.network) xs)+ (parse (hex ("02" <> "00000000" <> "00" <> "03" <> "05" <> "0102030405" <> "0000" <> "e215104d" <> "01" <> "01" <> "04" <> "0a000001" <> "208d")))+ `shouldBe` Right [AddrTorV2, AddrIPv4]+ it "has no addr form for an IPv6 entry that holds an IPv4-mapped address" $+ fromNetworkAddressV2 (NetworkAddressV2 0 0 AddrIPv6 (hex "00000000000000000000ffff0a000001") 8333) `shouldBe` Nothing+ it "has no addr form for an IPv6 entry that holds an OnionCat address" $+ fromNetworkAddressV2 (NetworkAddressV2 0 0 AddrIPv6 (hex "fd87d87eeb4301020304050607080910") 8333) `shouldBe` Nothing+ it "turns an OnionCat addr entry into a Tor v2 entry of ten bytes" $+ toNetworkAddressV2 (0, NetworkAddress 0 (either error id (parse (hex "fd87d87eeb4301020304050607080910" <> hex "208d"))))+ `shouldBe` NetworkAddressV2 0 0 AddrTorV2 (hex "01020304050607080910") 8333
test/Haskoin/ScriptSpec.hs view
@@ -120,6 +120,13 @@ gKeyHex :: Text gKeyHex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" +-- | The x and y coordinates of the same key. Its y is even, so its hybrid+-- form starts with 0x06.+gUncompressedXY :: Text+gUncompressedXY =+ "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\+ \483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"+ gKey :: Ctx -> PublicKey gKey ctx = either error id $ unmarshal ctx $ hex gKeyHex @@ -472,7 +479,11 @@ ("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"),+ ("P2PKH with a 21-byte hash", "76a915" <> T.replicate 21 "ab" <> "88ac"),+ ("P2SH with a 19-byte hash", "a913" <> T.replicate 19 "ab" <> "87"), ("P2SH with a 21-byte hash", "a915" <> T.replicate 21 "ab" <> "87"),+ ("P2SH32 with a 31-byte hash", "aa1f" <> T.replicate 31 "cd" <> "87"),+ ("P2SH32 with a 33-byte hash", "aa21" <> T.replicate 33 "cd" <> "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"),@@ -481,6 +492,10 @@ ("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"),+ -- Bitcoin Core reads a count as a minimal script number: 16 is OP_16,+ -- and 17 is a direct push of one byte.+ ("multisig with 16 as a byte push", "0110" <> T.concat (replicate 16 ("21" <> gKeyHex)) <> "60ae"),+ ("multisig with 17 pushed with OP_PUSHDATA1", "4c0111" <> T.concat (replicate 17 ("21" <> gKeyHex)) <> "0111ae"), ("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"),@@ -512,6 +527,31 @@ (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 "writes such a multisig back with direct pushes, which are other bytes" $+ -- A ScriptOutput does not keep the form of a push. The result must not+ -- be hashed or signed in place of the script that was read.+ (encodeHexOutput =<< decodeHexOutput ("514c21" <> gKeyHex <> "51ae"))+ `shouldBe` Right ("5121" <> gKeyHex <> "51ae")+ it "writes a data carrier back with the shortest pushes, which can be other bytes" $+ (encodeHexOutput =<< decodeHexOutput "6a4c0401020304") `shouldBe` Right "6a0401020304"+ it "does not decode a key that is not a point of the curve, where Bitcoin Core only checks its size" $ do+ -- decodescript of Bitcoin Core 32.1 says pubkey, pubkey, pubkey and+ -- multisig for these four scripts. A PublicKey of this library is a+ -- point, so they have no ScriptOutput.+ -- The key of private key 1 in hybrid form: 06, x and y.+ decodeHexOutput ("4106" <> gUncompressedXY <> "ac") `shouldSatisfy` isLeft+ -- 33 and 65 bytes with a valid first byte that are not on the curve+ decodeHexOutput ("2102" <> T.replicate 32 "ff" <> "ac") `shouldSatisfy` isLeft+ decodeHexOutput ("4104" <> T.replicate 64 "ff" <> "ac") `shouldSatisfy` isLeft+ -- A 1-of-2 multisig whose second key is not on the curve+ decodeHexOutput ("5121" <> gKeyHex <> "2102" <> T.replicate 32 "ff" <> "52ae") `shouldSatisfy` isLeft+ it "does not decode OP_RETURN followed by OP_RESERVED, which Bitcoin Core counts as a push" $+ decodeHexOutput "6a50" `shouldSatisfy` isLeft+ it "does not decode a Bitcoin Cash output that holds tokens" $+ -- The token prefix 0xef, a 32-byte category, the bitfield 0x10 for a+ -- fungible amount, the amount 100, then a pay-to-public-key-hash script+ decodeHexOutput ("ef" <> T.replicate 32 "aa" <> "1064" <> "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac")+ `shouldSatisfy` isLeft it "has 20 as the most multisig keys" $ maxMultisigKeys `shouldBe` 20 it "refuses to encode a multisig that no script can express" $ do@@ -535,16 +575,38 @@ 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"+ `shouldBe` Right (PayScriptHash "4733f37cf4db86fbc2efed2500b4f4e49f312023") it "hashes the witness script of the P2WSH example of BIP 143" $ toP2WSH bip143WitnessScript- `shouldBe` PayWitnessScriptHash "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"+ `shouldBe` Right (PayWitnessScriptHash "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0")+ it "toP2SH takes a redeem script of 520 bytes, the most a push can hold" $+ -- 520 one-byte opcodes. HASH160 computed with Python (hashlib, embit).+ toP2SH (Script (replicate 520 OP_NOP))+ `shouldBe` Right (PayScriptHash "b5c90009be7bc8faa0add03f76ee9c88fd36d6b4")+ it "toP2SH refuses a redeem script of 521 bytes" $+ toP2SH (Script (replicate 521 OP_NOP))+ `shouldBe` Left "The redeem script is 521 bytes long, and a pay-to-script-hash redeem script can have at most 520"+ it "toP2WSH takes a witness script of 10,000 bytes, the most the interpreter runs" $+ toP2WSH (Script (replicate 10000 OP_NOP))+ -- SHA256 of 10,000 bytes 0x61, computed with Python (hashlib)+ `shouldBe` Right (PayWitnessScriptHash "27dd1f61b867b6a0f6e9d8a41c43231de52107e53ae424de8f847b821db4b711")+ it "toP2WSH refuses a witness script of 10,001 bytes" $+ toP2WSH (Script (replicate 10001 OP_NOP))+ `shouldBe` Left "The witness script is 10001 bytes long, and a pay-to-witness-script-hash witness script can have at most 10000"+ it "p2shScriptHash and p2wshScriptHash give the hashes those outputs commit to" $ do+ p2shScriptHash (Script [OP_0, opPushData (hex "79091972186c449eb1ded22b78e40d009bdf0089")])+ `shouldBe` Right "4733f37cf4db86fbc2efed2500b4f4e49f312023"+ p2wshScriptHash bip143WitnessScript+ `shouldBe` Right "5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"+ it "names the two limits" $ do+ maxRedeemScriptSize `shouldBe` 520+ maxWitnessScriptSize `shouldBe` 10000 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"+ (addrToText btc <$> payToScriptAddress ctx (sortMulSig ctx unsorted))+ `shouldBe` Right (Just "39bgKC7RFbpoCRbtD5KEdkYKtNyhpsNa3Z") it "leaves any other output unchanged" $ sortMulSig ctx (PayPKHash gHash) `shouldBe` PayPKHash gHash prop "sorts the keys by their serialized bytes" $@@ -627,6 +689,14 @@ ("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 signature before a nested P2WSH program", sigAllPushHex <> "2200205d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"),+ -- BIP 141: the script must be exactly a push of the program. A push+ -- with OP_PUSHDATA1 is invalid by consensus.+ ("a nested P2WPKH program pushed with OP_PUSHDATA1", "4c16001479091972186c449eb1ded22b78e40d009bdf0089"),+ ("a nested P2WSH program pushed with OP_PUSHDATA1", "4c2200205d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0"),+ -- The redeem script must be in the form this library writes, or its+ -- hash would change when it is written back.+ ("a multisig redeem script whose key is pushed with OP_PUSHDATA1", "0026514c21" <> gKeyHex <> "51ae"), ("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")@@ -646,6 +716,22 @@ it "rejects scripts that are not a standard input" $ forM_ nonStandardInputs $ \(name, h) -> (name, decodeHexInput h) `shouldSatisfy` (isLeft . snd)+ it "reads the multisig redeem script in its direct push form, which the one above is not" $+ decodeHexInput ("00255121" <> gKeyHex <> "51ae")+ `shouldBe` Right (ScriptHashInput (SpendMulSig []) (PayMulSig [g] 1))+ it "reads a redeem script pushed with OP_PUSHDATA1, and writes it back with a direct push" $ do+ -- The push of the redeem script is not part of what is hashed, so its+ -- form does not change the address of the input+ let decoded = decodeHexInput ("004c255121" <> gKeyHex <> "51ae")+ decoded `shouldBe` Right (ScriptHashInput (SpendMulSig []) (PayMulSig [g] 1))+ (encodeHexInput <$> decoded) `shouldBe` Right ("00255121" <> gKeyHex <> "51ae")+ it "does not decode an input whose signature only consensus accepts" $ do+ -- A high S: r = 1 and s = n - 1, which is strict DER+ let highS = "3026020101022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"+ -- 40 bytes of signature and the hash type byte: a push of 41 bytes+ decodeHexInput ("29" <> highS <> "01" <> "21" <> gKeyHex) `shouldSatisfy` isLeft+ -- An undefined hash type byte, 0x04+ decodeHexInput ("47" <> sigDerHex <> "04" <> "21" <> gKeyHex) `shouldSatisfy` isLeft 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.@@ -736,6 +822,18 @@ \f -> toEnum (fromEnum f) `shouldBe` f it "has no flag for other values" $ evaluate (toEnum 0x05 :: SigHashFlag) `shouldThrow` anyErrorCall+ it "lists the flags in the order of their constructors" $ do+ [minBound .. maxBound] `shouldBe` [SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_FORKID, SIGHASH_ANYONECANPAY]+ [SIGHASH_ALL ..] `shouldBe` [SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_FORKID, SIGHASH_ANYONECANPAY]+ [SIGHASH_NONE .. SIGHASH_FORKID] `shouldBe` [SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_FORKID]+ [SIGHASH_ALL, SIGHASH_SINGLE ..] `shouldBe` [SIGHASH_ALL, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY]+ [SIGHASH_ANYONECANPAY, SIGHASH_FORKID ..] `shouldBe` [SIGHASH_ANYONECANPAY, SIGHASH_FORKID, SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ALL]+ it "steps from a flag to the next and the previous one" $ do+ -- The values of SIGHASH_SINGLE and SIGHASH_FORKID are 0x03 and 0x40+ succ SIGHASH_SINGLE `shouldBe` SIGHASH_FORKID+ pred SIGHASH_FORKID `shouldBe` SIGHASH_SINGLE+ evaluate (succ SIGHASH_ANYONECANPAY) `shouldThrow` anyErrorCall+ evaluate (pred SIGHASH_ALL) `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)@@ -750,32 +848,57 @@ 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" $+ -- The digests below were computed with Python (hashlib) from the text of+ -- BIP 143 and from the legacy algorithm, not with this library.+ it "computes the BIP 143 digest of that input with SIGHASH_ALL and SIGHASH_ANYONECANPAY" $ txSigHashForkId btc bip143Tx bip143ScriptCode bip143Value 1 (setAnyoneCanPay sigHashAll)- `shouldNotBe` Right bip143Digest+ `shouldBe` Right "fc5b6bbc855883bcfdaefb77071740ccde4929f15e6a13286584e779b2529d91"+ it "writes the hash type of the BIP 143 digest as four bytes on Bitcoin, as Bitcoin Core does" $+ -- The preimage ends in 01 01 00 00 for the hash type 0x101. A signature+ -- only carries one byte, so no signature matches this digest.+ txSigHashForkId btc bip143Tx bip143ScriptCode bip143Value 1 0x101+ `shouldBe` Right "991a362ee6f2b8a004118aa1f098165a84df10b434f5d14c9cb18661309423ef"+ it "writes the hash type of the legacy digest as four bytes" $ do+ txSigHash btc bip143Tx bip143ScriptCode bip143Value 1 sigHashAll+ `shouldBe` Right "c46030820cbc48402a47cc5b5d3d41648f4e3a711f56b804d601d09dc112a6a4"+ txSigHash btc bip143Tx bip143ScriptCode bip143Value 1 0x101+ `shouldBe` Right "8e4ac2d7b4f759fdd01de9b78a2c46fe361316f397b214c7f0f98f073128aa4c"+ it "computes the fork id digest of Bitcoin Cash for each of its six hash types" $+ -- The fork id of Bitcoin Cash is 0, so the last four bytes of the+ -- preimage are the hash type byte and three zero bytes+ forM_+ [ (0x41, "467f411d178762db122a6aced76370a1c8324355bf0796502bf82eeaeda86a35"),+ (0x42, "c0876aa9dfd131ac207be062e389741416a87a5d1b28e4857c178990454dd498"),+ (0x43, "abb61ba86e14313425d25846ed3a30904de1f081e013d80c385e165c2af1e020"),+ (0xc1, "a5890ce40dc95a89717ae6fa3c9d60bcf9372539058c7e9a0cd8ff7909723326"),+ (0xc2, "e58ebfd50f957a45c5f68439e55c6ae6054a3310ff2a116efa07d2bda2527bd3"),+ (0xc3, "4e303851715b6ee36582740f43cc288c969e88afc641f14e3e8e68d32c406c1b")+ ]+ $ \(sh, digest) ->+ (sh, txSigHashForkId bch bip143Tx bip143ScriptCode bip143Value 1 sh) `shouldBe` (sh, Right digest) 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" $+ it "removes OP_CODESEPARATOR from the script code of the legacy digest, as SerializeScriptCode of Bitcoin Core does" $+ -- The same digest as for the script code without the opcode txSigHash btc bip143Tx (Script (OP_CODESEPARATOR : bip143ScriptCode.ops)) bip143Value 1 sigHashAll- `shouldBe` txSigHash btc bip143Tx bip143ScriptCode bip143Value 1 sigHashAll+ `shouldBe` Right "c46030820cbc48402a47cc5b5d3d41648f4e3a711f56b804d601d09dc112a6a4" it "keeps OP_CODESEPARATOR in the script code of the BIP 143 digest" $+ -- The script code is then 26 bytes: ab, then the 25 bytes of the script txSigHashForkId btc bip143Tx (Script (OP_CODESEPARATOR : bip143ScriptCode.ops)) bip143Value 1 sigHashAll- `shouldNotBe` Right bip143Digest+ `shouldBe` Right "f58633c60526565d17f4a38d202ab3cfc26fb900abd2f49cf2252488b5240964" 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.+ it "uses the fork id digest on Bitcoin Cash" $+ -- The digest of the table above for the hash type 0x41 txSigHash bch bip143Tx bip143ScriptCode bip143Value 1 (setForkIdFlag sigHashAll)- `shouldNotBe` Right bip143Digest+ `shouldBe` Right "467f411d178762db122a6aced76370a1c8324355bf0796502bf82eeaeda86a35" it "uses the legacy digest on Bitcoin even with the SIGHASH_FORKID flag" $+ -- The legacy digest with the four hash type bytes 41 00 00 00 txSigHash btc bip143Tx bip143ScriptCode bip143Value 1 (setForkIdFlag sigHashAll)- `shouldNotBe` Right bip143Digest+ `shouldBe` Right "5e0f360ad7f9a80982d2529061f963a8afe63dbb010eb01a94abfa4626797129" {- Transaction signatures -} @@ -802,8 +925,32 @@ 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 a signature with a high S, the LOW_S rule of the relay policy" $ do+ -- r = 1 and s = n - 1. The encoding is strict DER, and consensus+ -- accepts it. decodeStrictSig reads it, decodeTxSig does not.+ let highS = "3026020101022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"+ decodeTxSig btc ctx (hex (highS <> "01"))+ `shouldBe` Left "Failed reading: Non-canonical signature: S value is unnecessarily high\nEmpty call stack\n"+ isJust (decodeStrictSig ctx (hex highS)) `shouldBe` True+ it "names the rule when it rejects an undefined hash type byte, the STRICTENC rule" $+ decodeTxSig btc ctx (hex (sigDerHex <> "04"))+ `shouldBe` Left "Failed reading: Non-canonical signature: unknown hashtype byte\nEmpty call stack\n"+ it "rejects a signature whose r is padded with a zero byte, the DERSIG rule of BIP 66" $+ -- 02 02 00 01 is the integer 1 with a needless leading zero+ decodeTxSig btc ctx (hex "300702020001020101" <> "\x01") `shouldSatisfy` isLeft it "rejects bytes after the hash type byte" $ decodeTxSig btc ctx (hex (sigDerHex <> "0101")) `shouldSatisfy` isLeft+ it "rejects bytes after the hash type byte in the binary form too" $+ (unmarshal (btc, ctx) (hex (sigDerHex <> "01deadbeef")) :: Either String TxSignature)+ `shouldSatisfy` isLeft+ it "does not read a Schnorr signature on Bitcoin Cash, which this type cannot hold" $+ -- 64 bytes and the hash type byte 0x41. Bitcoin Cash Node takes a+ -- 64-byte signature as Schnorr.+ decodeTxSig bch ctx (B.replicate 64 0x11 <> "\x41") `shouldSatisfy` isLeft+ it "does not read SIGHASH_UTXOS on Bitcoin Cash, which the digest does not support" $+ -- 0x61 is SIGHASH_ALL with SIGHASH_UTXOS and SIGHASH_FORKID+ decodeTxSig bch ctx (hex (sigDerHex <> "61"))+ `shouldBe` Left "Failed reading: Non-canonical signature: unknown hashtype byte\nEmpty call stack\n" 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" $@@ -837,10 +984,27 @@ forAll (arbitraryScriptOutput net ctx) $ \so -> unmarshal ctx (marshal ctx so) `shouldBe` Right so +-- | The file @data/script_tests.json@ is not the @script_tests.json@ of+-- Bitcoin Core, which has more than a thousand vectors for its interpreter.+-- It is a selection of 86 of them on the standard templates, edited for a+-- library without an interpreter:+--+-- * 41 vectors carry the word DISABLED in their flags and do not run. They+-- need script execution or a rule this library does not have.+--+-- * 9 vectors that Bitcoin Core expects to pass without a policy flag have+-- the result NONSTRICT in place of OK. The signature or the input of each+-- one breaks a relay rule, such as strict DER, a low S, a defined hash type+-- or an empty dummy element. This library reads the relay form only, so it+-- refuses them.+--+-- * One vector, about the fork id flag, is not in Bitcoin Core.+--+-- So 45 vectors run: 9 valid spends and 36 refusals. scriptSpec :: Network -> Ctx -> Spec scriptSpec net ctx = when (net.name == "btc") $- it "can verify standard scripts from script_tests.json file" $ do+ it "verifies the 45 standard-template vectors selected from script_tests.json of Bitcoin Core" $ do xs <- readTestFile "script_tests.json" :: IO [A.Value] let vectorsA = mapMaybe (A.decode . A.encode) xs ::@@ -851,18 +1015,38 @@ vectors = map (\(a, b, c, d, e) -> ([0], a, b, c, d, e)) vectorsA <> vectorsB+ -- A vector is disabled by the word DISABLED in its flags+ enabled = [v | v@(_, _, _, flags, _, _) <- vectors, not ("DISABLED" `isInfixOf` flags)] length vectors `shouldBe` 86- 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 scriptSig = parseScript siStr- scriptPubKey = parseScript soStr- out = unmarshal ctx scriptPubKey- sat = val * 100000000- 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+ length enabled `shouldBe` 45+ length [() | (_, _, _, _, "OK", _) <- enabled] `shouldBe` 9+ forM_ enabled $ \([val], siStr, soStr, _, res, desc) -> do+ let scriptSig = parseScript siStr+ scriptPubKey = parseScript soStr+ out = unmarshal ctx scriptPubKey+ sat = val * 100000000+ 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+ -- A vector refused for the form of its input, not for a wrong+ -- signature, must already fail to read as a standard input. That+ -- tells such a refusal from an unrelated one.+ when (res `elem` encodingErrors) $+ assertBool (desc <> ": the input is not in the relay form") $+ isLeft (decodeInput net ctx =<< runGetS deserialize scriptSig)+ where+ -- The results of Bitcoin Core that name a rule on the form of an input,+ -- and NONSTRICT, which this file uses for a form only consensus accepts+ encodingErrors =+ [ "SIG_DER",+ "SIG_HASHTYPE",+ "SIG_NULLDUMMY",+ "SIG_PUSHONLY",+ "CLEANSTACK",+ "ILLEGAL_FORKID",+ "NONSTRICT"+ ] forkIdScriptSpec :: Network -> Ctx -> Spec forkIdScriptSpec net ctx =@@ -941,14 +1125,35 @@ let eitherSig :: Either String TxSignature eitherSig = decodeTxSig net ctx sig in eitherSig `shouldSatisfy` isRight- it "can detect non-strict signatures" $ do- xs <- readTestFile "sig_nonstrict.json"- let vectors = mapMaybe decodeHex xs- length vectors `shouldBe` 17- forM_ vectors $ \sig ->- let eitherSig = decodeTxSig net ctx sig- in eitherSig `shouldSatisfy` isLeft+ it "refuses the non-strict signatures, each for the rule its label names" $ do+ xs <- readTestFile "sig_nonstrict.json" :: IO [Text]+ -- The file is a list of labels, each followed by the signatures it+ -- describes. A signature is DER followed by a hash type byte.+ let labelled = [(label, sig) | (label, Just sig) <- zip (labelsOf xs) (map decodeHex xs)]+ length labelled `shouldBe` 17+ forM_ labelled $ \(label, sig) -> do+ let name = cs label <> " " <> cs (encodeHex sig)+ strictDer = isJust (decodeStrictSig ctx (B.init sig))+ refusal = either id (const "accepted") (decodeTxSig net ctx sig)+ case label of+ -- Strict DER, and a hash type byte that is not defined+ "hashtype:" -> do+ assertBool name strictDer+ assertBool name ("unknown hashtype byte" `isInfixOf` refusal)+ -- Strict DER, and an S above half the curve order+ "High S:" -> do+ assertBool name strictDer+ assertBool name ("S value is unnecessarily high" `isInfixOf` refusal)+ -- Every other label names a rule of BIP 66+ _ -> do+ assertBool name (not strictDer)+ assertBool name (refusal /= "accepted") +-- | The label that describes each entry of @sig_nonstrict.json@: an entry+-- that is not hex is a label, and it holds for the entries after it.+labelsOf :: [Text] -> [Text]+labelsOf = drop 1 . scanl (\label x -> if isNothing (decodeHex x) then x else label) ""+ txSigHashSpec :: Network -> Spec txSigHashSpec net = when (net.name == "btc") $@@ -973,12 +1178,17 @@ res = eitherToMaybe . runGetS deserialize . B.reverse =<< decodeHex (cs resStr)+ -- A vector that did not decode would compare Nothing with Nothing+ res `shouldSatisfy` isJust eitherToMaybe (txSigHash net tx s 0 i sh) `shouldBe` res txSigHashForkIdSpec :: Network -> Spec txSigHashForkIdSpec net = when (net.name == "btc") $- it "can produce valid sighashes from forkid_sighash.json test vectors" $ do+ -- The file holds the thirteen digests of the examples of BIP 143. None+ -- has the SIGHASH_FORKID bit, so they run on Bitcoin. The fork id digest+ -- of Bitcoin Cash has literal values in the digest tests above.+ it "computes the thirteen BIP 143 digests of forkid_sighash.json" $ do xs <- readTestFile "forkid_sighash.json" :: IO [A.Value] let vectors = mapMaybe (A.decode . A.encode) xs ::@@ -998,6 +1208,7 @@ eitherToMaybe . runGetS deserialize =<< decodeHex (cs scpStr) sh = fromIntegral shI res = eitherToMaybe . runGetS deserialize =<< decodeHex (cs resStr)+ res `shouldSatisfy` isJust eitherToMaybe (txSigHashForkId net tx s val i sh) `shouldBe` res sigHashSpec :: Network -> Ctx -> Spec@@ -1057,7 +1268,7 @@ b = do o <- s d <- eitherToMaybe $ decodeOutput ctx o- addrToText btc $ payToScriptAddress ctx d+ addrToText btc =<< eitherToMaybe (payToScriptAddress ctx d) sigDecodeMap :: Network -> Ctx -> (Text, Int) -> Spec sigDecodeMap net ctx (_, i) =
test/Haskoin/Transaction/PsbtSpec.hs view
@@ -13,9 +13,9 @@ import Data.Aeson ((.:), (.:?)) import Data.Aeson qualified as Json import Data.ByteString (ByteString)+import Data.Bits (xor) import Data.ByteString qualified as B-import Data.Either (isLeft)-import Data.List (isSuffixOf)+import Data.List (isPrefixOf, isSuffixOf) import Crypto.Hash (Digest, SHA256, digestFromByteString) import Data.Bytes.Put (runPutS) import Data.Bytes.Serial (serialize)@@ -23,8 +23,9 @@ import Data.Maybe (fromJust, fromMaybe, isJust, listToMaybe, mapMaybe) import Data.Serialize qualified as S import Data.Text (Text)+import Data.Text qualified as T import Data.Text.Encoding (encodeUtf8)-import Data.Word (Word32, Word64)+import Data.Word (Word32, Word64, Word8) import Haskoin.Address import Haskoin.Crypto import Haskoin.Network.Constants@@ -97,6 +98,17 @@ psbtOfVector :: Vector -> IO Psbt psbtOfVector v = expectRight (decodePsbt (bytesOf v)) +-- | The secret keys 1, 2 and 3 of the signer tests. Their public keys are+-- the points G, 2G and 3G of the curve.+secretOne, secretTwo, secretThree :: SecKey+secretOne = "0000000000000000000000000000000000000000000000000000000000000001"+secretTwo = "0000000000000000000000000000000000000000000000000000000000000002"+secretThree = "0000000000000000000000000000000000000000000000000000000000000003"++-- | The compressed public key of a secret key+compressedKey :: Ctx -> SecKey -> PublicKey+compressedKey ctx secret = PublicKey (derivePubKey ctx secret) True+ -- | The map of the input, or of the output, at the index inputAt, outputAt :: Int -> Psbt -> PsbtMap inputAt i psbt = psbt.inputs !! i@@ -124,6 +136,11 @@ fieldsSpec ctx vectors signingSpec ctx signerChecksSpec ctx+ signerScopeSpec ctx+ coinsSpec ctx+ version2FieldsSpec+ taprootChecksSpec ctx vectors371+ decoderChecksSpec taprootSpec ctx vectors371 complexSpec ctx core32Spec ctx@@ -221,11 +238,41 @@ {- The vectors -} +-- | Why each invalid PSBT of BIP 174 is refused, in the order of the BIP,+-- as the start of the reason. The cases are: no magic bytes, a map+-- missing, a script in the unsigned transaction, no unsigned transaction,+-- a key twice, twelve keys whose data is not what their type takes, and+-- an unsigned transaction that is not in the serialization without witness+-- or not whole.+refusals174 :: [String]+refusals174 =+ [ "Not a PSBT: the bytes do not start with the magic bytes of BIP 174",+ "The PSBT ends inside output 0",+ "The unsigned transaction has an input with a script",+ "The global map has no unsigned transaction",+ "Input 0 has the key 0x00 twice",+ "The global map: the key 0x00 with the data 01 carries data",+ "Input 0: the key 0x01 with the data 00 carries data",+ "Input 0: the key 0x02 with the data 03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd is not a public key",+ "Input 0: the key 0x04 with the data 00 carries data",+ "Input 0: the key 0x05 with the data 00 carries data",+ "Input 0: the key 0x06 with the data 03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd is not a public key",+ "Input 0: the key 0x00 with the data 00 carries data",+ "Input 0: the key 0x07 with the data 00 carries data",+ "Input 1: the key 0x08 with the data 00 carries data",+ "Output 0: the key 0x02 with the data 03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca587 is not a public key",+ "Input 0: the key 0x03 with the data 00 carries data",+ "Output 0: the key 0x00 with the data 00 carries data",+ "Output 1: the key 0x01 with the data 0025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d",+ "The unsigned transaction does not decode",+ "The unsigned transaction does not decode"+ ]+ 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 invalid PSBTs of BIP 174 are refused, each for its reason" $+ forM_ (zip vectors.invalid refusals174) $ \(v, reason) ->+ it v.vecCase $ decodePsbt (bytesOf v) `shouldSatisfy` either (reason `isPrefixOf`) (const False) describe "The valid PSBTs of BIP 174 are read, and written back byte for byte" $ forM_ vectors.valid $ \v ->@@ -252,10 +299,65 @@ 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+ -- BIP 174: the extractor "checks whether all inputs have complete+ -- scriptSigs and scriptWitnesses", and otherwise "must not modify the+ -- PSBT". Bitcoin Core gives no transaction then (FinalizeAndExtractPSBT).+ it "the transaction extractor refuses a PSBT with an input that is not final" $ do psbt <- psbtOfVector (creator vectors)- psbtTransaction psbt `shouldBe` Right psbt.unsignedTx+ psbtTransaction psbt `shouldBe` Left "Input 0 is not final" + it "the transaction of a PSBT that is still being signed has the final scripts of the inputs that have them" $ do+ unsigned <- psbtOfVector (creator vectors)+ psbtPartialTransaction unsigned `shouldBe` Right unsigned.unsignedTx+ final <- psbtOfVector (finalizer vectors)+ -- The second input loses its final script and witness+ let halfFinal = withInputs [inputAt 0 final, Map.delete (Key 0x07 "") (Map.delete (Key 0x08 "") (inputAt 1 final))] final+ psbtTransaction halfFinal `shouldBe` Left "Input 1 is not final"+ (map (B.null . (.script)) . (.inputs) <$> psbtPartialTransaction halfFinal) `shouldBe` Right [False, True]+ ((.witness) <$> psbtPartialTransaction halfFinal) `shouldBe` Right []++ -- The updater's PSBT gives the coin of its second input, a P2SH-P2WSH+ -- one, as a witness output only, as BIP 174 did before CVE-2020-14199+ -- was known. A signer that wants previous transactions refuses it.+ it "a signer that trusts witness outputs makes the first signer's PSBT of the BIP from the updater's, and the second signer's" $ do+ psbt <- psbtOfVector (updaterWithSigHash vectors)+ let master = fromJust (xPrvImport btcTest "tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF")+ trusting = (xPrvSigner ctx master Nothing) {requirePreviousTx = False}+ holding paths = trusting {keyOfRecord = \public derivation -> if derivation.path `elem` paths then trusting.keyOfRecord public derivation else Nothing}+ (encodePsbt <$> signPsbt btcTest ctx (holding ["m/0'/0'/0'", "m/0'/0'/2'"]) psbt) `shouldBe` Right (bytesOf (firstSigner vectors))+ (encodePsbt <$> signPsbt btcTest ctx (holding ["m/0'/0'/1'", "m/0'/0'/3'"]) psbt) `shouldBe` Right (bytesOf (secondSigner vectors))+ signPsbt btcTest ctx (xPrvSigner ctx master Nothing) psbt+ `shouldBe` Left "Input 1: the coin is given as a witness output only, and the signer requires the previous transaction of a SegWit version 0 coin"++ -- The four cases the BIP lists under "Fails Signer checks", signed+ -- with the master key of the BIP. The input at fault never gains a+ -- signature. The numbers are the partial signatures of each input.+ describe "a signer adds no signature to the input at fault of the PSBTs that fail the signer checks of the BIP" $ do+ let master = fromJust (xPrvImport btcTest "tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF")+ trusting = (xPrvSigner ctx master Nothing) {requirePreviousTx = False}+ counts = mapM (fmap length . partialSignatures ctx) . (.inputs)+ failing i = psbtOfVector (vectors.valid !! i)+ -- No record names a key, so the signer holds none: nothing is signed+ it "a witness output for a legacy coin" $ do+ psbt <- failing 10+ counts psbt `shouldBe` Right [0, 0]+ (counts =<< signPsbt btcTest ctx trusting psbt) `shouldBe` Right [0, 0]+ -- The redeem script of input 0 is no standard script, and does not+ -- hash to the coin. Input 1 is sound and is signed.+ it "a redeem script that does not match the coin, with the previous transaction" $ do+ psbt <- failing 11+ counts psbt `shouldBe` Right [1, 1]+ (counts =<< signPsbt btcTest ctx trusting psbt) `shouldBe` Right [1, 2]+ it "a redeem script that does not match the coin, with the witness output" $ do+ psbt <- failing 12+ signPsbt btcTest ctx trusting psbt `shouldBe` Left "Input 1: the redeem script of the input does not hash to its coin"+ -- The witness script of input 1 is no standard script. Input 0 is+ -- sound and is signed.+ it "a witness script that does not match the redeem script" $ do+ psbt <- failing 13+ counts psbt `shouldBe` Right [1, 1]+ (counts =<< signPsbt btcTest ctx trusting psbt) `shouldBe` Right [2, 1]+ -- 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@@ -669,7 +771,8 @@ -- | 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+-- transaction, or the output alone for a Taproot coin, whose signature+-- commits to the output of every input (BIP 341) spendingPsbt :: Ctx -> ScriptOutput -> (Psbt, [(ScriptOutput, Word64, OutPoint)]) spendingPsbt ctx coinScript = (Psbt Version0 tx Map.empty [input] [Map.empty, Map.empty], [(coinScript, 200000000, outpoint)]) where@@ -684,9 +787,9 @@ ] [] 1257139- input- | isSegwit coinScript = insertWitnessUtxo (TxOut 200000000 (marshal ctx coinScript)) Map.empty- | otherwise = insertNonWitnessUtxo parent Map.empty+ input = case coinScript of+ PayWitness 1 _ -> insertWitnessUtxo (TxOut 200000000 (marshal ctx coinScript)) Map.empty+ _ -> 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@@ -699,7 +802,8 @@ 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+ acceptedSigHashes = defaultSigHashes,+ requirePreviousTx = True } -- | Whether the transaction of the PSBT, once signed with the keys and@@ -707,7 +811,7 @@ 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+ final <- finalizePsbt net ctx signed tx <- psbtTransaction final return (verifyStdTx net ctx tx coins) @@ -732,19 +836,23 @@ === (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), _) ->+ -- At most 7 keys: the redeem script of more uncompressed keys would be+ -- over the 520 bytes a P2SH redeem script can have+ forAll ((,) <$> arbitraryP2SHMSParam <*> 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)+ (psbt, coins) = spendingPsbt ctx (either error id (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 + -- The keys are compressed: a SegWit version 0 coin is not signed with+ -- an uncompressed key, which 'signerScopeSpec' tests prop "a P2WPKH coin, and a P2SH-P2WPKH one with its redeem script, signed and finalized, verify" $- forAll (arbitraryKeyPair ctx) $ \(secret, public) ->+ forAll (arbitraryKeyPairC 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)+ (nested, nestedCoins) = spendingPsbt ctx (either error id (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@@ -752,12 +860,12 @@ 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 ->+ forAll (vectorOf n (arbitraryKeyPairC ctx)) $ \pairs -> let witness = either error id $ encodeOutput ctx (PayMulSig (snd <$> pairs) m)- program = toP2WSH witness+ program = either error id (toP2WSH witness) records = foldr (\(_, public) f -> recordOf ctx public . f) id pairs (native, nativeCoins) = spendingPsbt ctx program- (nested, nestedCoins) = spendingPsbt ctx (toP2SH programScript)+ (nested, nestedCoins) = spendingPsbt ctx (either error id (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@@ -765,8 +873,9 @@ .&&. 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)))+ let secret = PrivateKey secretOne True+ public = derivePublicKey ctx secret+ (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}@@ -777,18 +886,19 @@ (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)))+ let secret = PrivateKey secretOne True+ public = derivePublicKey ctx secret+ (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)))+ let public = compressedKey ctx secretOne+ (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"+ signPsbt btc ctx (Signer (\_ _ -> Nothing) (const Nothing) defaultSigHashes True) wrong `shouldBe` Left "Input 0: the previous transaction is not the one the input 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@@ -812,11 +922,13 @@ {- 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+-- | The PSBTs of a file that scripts/create_complex_psbt_data.sh wrote: a+-- transaction of nine coins of every kind, signed by twelve wallets of+-- Bitcoin Core one at a time, their combination, its finalization and the+-- transaction it gives data Complex = Complex- { signedPsbts :: [Psbt],+ { initialPsbt :: Psbt,+ signedPsbts :: [Psbt], combinedPsbt :: Psbt, completePsbt :: Psbt, finalTx :: Tx@@ -825,7 +937,8 @@ 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 "initial_psbt"+ <*> 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")@@ -833,20 +946,44 @@ 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+complexSpec ctx = do+ -- data/complex_psbt.json is the file of an older Bitcoin Core, in+ -- version 0. Each wallet finalized the coins it could complete alone.+ describe "The PSBT of nine coins of every kind, which twelve wallets of 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"+ complexChecks ctx complex - it "finalizes into the PSBT Core finalized" $- finalizePsbt btc ctx complex.combinedPsbt `shouldBe` Right complex.completePsbt+ -- data/psbt_complex_core32.json is the file of Bitcoin Core 32.1, in+ -- version 2. The wallets signed with finalize=false, so that the+ -- combination holds partial signatures only.+ describe "The PSBT of nine coins of every kind, which twelve wallets of Bitcoin Core 32.1 signed without finalizing" $ do+ complex <- runIO (readTestFile "psbt_complex_core32.json" :: IO Complex)+ it "is a version 2 PSBT whose combination has no final input" $ do+ complex.combinedPsbt.version `shouldBe` Version2+ length complex.combinedPsbt.inputs `shouldBe` 9+ -- Types 0x07 and 0x08 are the final script and the final witness+ length [key | input <- complex.combinedPsbt.inputs, key <- Map.keys input, key.keyType `elem` [0x07, 0x08]] `shouldBe` 0+ complexChecks ctx complex - it "gives the transaction Core extracted" $- psbtTransaction complex.completePsbt `shouldBe` Right complex.finalTx+-- | What holds for both files: the library combines, finalizes and+-- extracts what Bitcoin Core combined, finalized and extracted+complexChecks :: Ctx -> Complex -> Spec+complexChecks ctx complex = do+ -- The PSBT Core created has no entry that a signed one lacks+ it "adds nothing to a signed PSBT when the PSBT Core created is combined with it" $+ mapM (combinePsbt complex.initialPsbt) complex.signedPsbts `shouldBe` Right complex.signedPsbts + 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@@ -906,9 +1043,10 @@ 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)))+ let secret = PrivateKey secretOne True+ public = derivePublicKey ctx secret+ otherPublic = compressedKey ctx secretTwo+ (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@@ -923,7 +1061,7 @@ 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"+ `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"@@ -960,7 +1098,7 @@ 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)+ (p2sh, _) = spendingPsbt ctx (either error id (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@@ -973,7 +1111,7 @@ 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)+ (p2wsh, _) = spendingPsbt ctx (either error id (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))@@ -998,7 +1136,7 @@ -- 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)+ (p2sh, p2shCoins) = spendingPsbt ctx (either error id (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)@@ -1020,9 +1158,6 @@ 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@@ -1033,7 +1168,6 @@ -- 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 -} @@ -1088,7 +1222,7 @@ psbt <- vectorAt 1 let sigHex = "bb53ec917bad9d906af1ba87181c48b86ace5aae2b53605a725ca74625631476fc6f5baedaf4f2ee0f477f36f58f3970d5b8273b7e497b97af2e3f125c97af34" sig = fromJust (schnorrSig (hexBytes sigHex))- tapKeySig (inputAt 0 psbt) `shouldBe` Right (Just (TaprootSignature sig sigHashDefault))+ tapKeySig (inputAt 0 psbt) `shouldBe` (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 ""]@@ -1125,7 +1259,7 @@ 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+ verifyScriptPathData ctx outKey (ScriptPathData Nothing [] leaf.script (odd v) 0xc0 internal proof Nothing) `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@@ -1209,9 +1343,10 @@ 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)+ let secret = PrivateKey secretOne True+ public = derivePublicKey ctx secret+ other = compressedKey ctx secretTwo+ 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"@@ -1232,3 +1367,544 @@ prop "combined with itself is itself" $ forAll (arbitraryPsbt btc ctx) $ \psbt -> combinePsbt psbt psbt == Right psbt++ -- The combiner of BIP 174 keeps every entry that either PSBT has. The+ -- key type 0x7e is one no map knows.+ prop "combined with a copy that has one more entry in every map, in either order, is that copy" $+ forAll (arbitraryPsbt btc ctx) $ \psbt ->+ let oneMore = Map.insert (Key 0x7e "combiner test") "one more entry"+ copy = Psbt psbt.version psbt.unsignedTx (oneMore psbt.globals) (oneMore <$> psbt.inputs) (oneMore <$> psbt.outputs)+ in combinePsbt psbt copy == Right copy && combinePsbt copy psbt == Right copy++{- What the decoder refuses -}++-- | The unsigned transaction of 'tinyPsbt': version 2, one input that+-- spends output 0 of the transaction whose id bytes are 00 01 … 1f, one+-- output of 1,000 satoshi, lock time 0. It is 82 bytes long.+tinyTx :: Text+tinyTx = "0200000001000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0000000000ffffffff01e803000000000000160014111111111111111111111111111111111111111100000000"++-- | A PSBT of version 0 over 'tinyTx', with the entries given, in hex, in+-- its global map, in the map of its input and in the map of its output.+-- An entry is a key after its length, then a value after its length. The+-- first entry is the transaction: 0x52 is its 82 bytes.+tinyPsbt :: Text -> Text -> Text -> ByteString+tinyPsbt globalEntries inputEntries outputEntries =+ hexBytes ("70736274ff" <> "010052" <> tinyTx <> globalEntries <> "00" <> inputEntries <> "00" <> outputEntries <> "00")++-- | The same payment as 'tinyPsbt' in version 2, with the entries given in+-- the map of its input: the transaction version 2, one input, one output+-- and the PSBT version 2 in the global map, the transaction id and the+-- output index 0 in the input, the amount and the script in the output.+tinyPsbt2 :: Text -> ByteString+tinyPsbt2 inputEntries =+ hexBytes $+ "70736274ff"+ <> ("01020402000000" <> "01040101" <> "01050101" <> "01fb0402000000" <> "00")+ <> (inputEntries <> "010e20000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" <> "010f0400000000" <> "00")+ <> ("010308e803000000000000" <> "01041600141111111111111111111111111111111111111111" <> "00")++-- | 33 bytes that start like a compressed public key and are no point of+-- the curve: the x coordinate ff…ff is above the field size+notAPoint :: Text+notAPoint = "02" <> T.replicate 32 "ff"++-- | A public key of the BIP 174 vectors+aPoint :: Text+aPoint = "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f"++-- | A transaction of 61 bytes that the input of 'tinyTx' does not spend:+-- one input, one output of 5,000 satoshi with the script OP_1+anotherTx :: Text+anotherTx = "0100000001" <> T.replicate 32 "00" <> "ffffffff" <> "00" <> "ffffffff" <> "01" <> "8813000000000000" <> "0151" <> "00000000"++-- | A leaf of a script tree as PSBT_OUT_TAP_TREE lists it: its depth, its+-- leaf version, and after its length of 34 bytes the script+-- @<32 bytes of 0x22> OP_CHECKSIG@. It is 37 bytes long.+treeLeaf :: Text -> Text -> Text+treeLeaf depth version = depth <> version <> "22" <> "20" <> T.replicate 32 "22" <> "ac"++decoderChecksSpec :: Spec+decoderChecksSpec =+ describe "What the decoder refuses, as Bitcoin Core 32.1 does" $ do+ it "reads the small PSBT that the cases below add one entry to, in both versions" $ do+ (encodePsbt <$> decodePsbt (tinyPsbt "" "" "")) `shouldBe` Right (tinyPsbt "" "" "")+ (encodePsbt <$> decodePsbt (tinyPsbt2 "")) `shouldBe` Right (tinyPsbt2 "")++ -- The type of a key is a compact size integer. fd0000 is 0, and fd0200+ -- is 2, over three bytes where one is enough.+ it "refuses a key whose type is not in its shortest form" $ do+ decodePsbt (tinyPsbt "03fd000001aa" "" "") `shouldBe` Left "The global map has a key whose type is not in its shortest form"+ decodePsbt (tinyPsbt "" "03fd020001aa" "") `shouldBe` Left "Input 0 has a key whose type is not in its shortest form"+ decodePsbt (tinyPsbt "" "" "09ff010000000000000001aa") `shouldBe` Left "Output 0 has a key whose type is not in its shortest form"++ -- fe announces a type of four more bytes, and the key has one more+ it "refuses a key that ends inside its type" $+ decodePsbt (tinyPsbt "" "02fe0101aa" "") `shouldBe` Left "Input 0 has a key that ends inside its type"++ -- Bitcoin Core reads no compact size above 0x02000000. fe00000003 is+ -- 0x03000000, and ff0000000001000000 is 2^32.+ it "refuses a key whose type is above the largest compact size Bitcoin Core reads" $ do+ decodePsbt (tinyPsbt "" "05fe0000000301aa" "") `shouldBe` Left "Input 0 has a key whose type is beyond any type"+ decodePsbt (tinyPsbt "" "09ff000000000100000001aa" "") `shouldBe` Left "Input 0 has a key whose type is beyond any type"++ -- fdfd00 is the type 253 and fe00000002 the type 0x02000000, both in+ -- their shortest form+ it "reads a key whose type takes three or five bytes, and writes it back" $ do+ (encodePsbt <$> decodePsbt (tinyPsbt "" "03fdfd0001aa" "")) `shouldBe` Right (tinyPsbt "" "03fdfd0001aa" "")+ (encodePsbt <$> decodePsbt (tinyPsbt "" "05fe0000000201aa" "")) `shouldBe` Right (tinyPsbt "" "05fe0000000201aa" "")++ -- The key is the type 0x02 and 33 bytes, 34 in all (0x22). The value is+ -- a signature of nine bytes, the smallest BIP 66 allows, with SIGHASH_ALL.+ it "refuses a partial signature under a key that is not a point of the curve" $+ decodePsbt (tinyPsbt "" ("2202" <> notAPoint <> "09300602010102010101") "")+ `shouldBe` Left ("Input 0: the key 0x02 with the data " <> T.unpack notAPoint <> " is not a public key")++ it "reads a partial signature that is in the encoding of BIP 66 with a defined sighash byte" $+ (encodePsbt <$> decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "09300602010102010101") ""))+ `shouldBe` Right (tinyPsbt "" ("2202" <> aPoint <> "09300602010102010101") "")++ -- "garbage" in ASCII, then no value at all, then the signature above+ -- with the sighash byte 0x04, which no node accepts+ it "refuses a partial signature that is not in the encoding of BIP 66, or whose sighash byte is not defined" $ do+ let refusal = Left ("Input 0: the value of the key 0x02 with the data " <> T.unpack aPoint <> " is not a signature in the encoding of BIP 66 with a defined sighash byte")+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "0767617262616765") "") `shouldBe` refusal+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "00") "") `shouldBe` refusal+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "09300602010102010104") "") `shouldBe` refusal++ -- Each value ends with the sighash byte 0x01, which is defined, and+ -- breaks one rule of BIP 66: bytes that are no DER at all, a length+ -- byte that does not cover the signature (0x07 for 6 bytes), an R that+ -- is negative (0x80), and an R with a zero byte it does not need (0001)+ it "refuses a partial signature with a defined sighash byte that breaks a rule of BIP 66" $ do+ let refusal = Left ("Input 0: the value of the key 0x02 with the data " <> T.unpack aPoint <> " is not a signature in the encoding of BIP 66 with a defined sighash byte")+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "0aaabbccddeeff00112201") "") `shouldBe` refusal+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "09300702010102010101") "") `shouldBe` refusal+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "09300602018002010101") "") `shouldBe` refusal+ decodePsbt (tinyPsbt "" ("2202" <> aPoint <> "0a30070202000102010101") "") `shouldBe` refusal++ -- The value is the fingerprint d90c6a4f and the path m/0'+ it "refuses a derivation record under a key that is not a point of the curve, on an input and on an output" $ do+ decodePsbt (tinyPsbt "" ("2206" <> notAPoint <> "08d90c6a4f00000080") "")+ `shouldBe` Left ("Input 0: the key 0x06 with the data " <> T.unpack notAPoint <> " is not a public key")+ decodePsbt (tinyPsbt "" "" ("2202" <> notAPoint <> "08d90c6a4f00000080"))+ `shouldBe` Left ("Output 0: the key 0x02 with the data " <> T.unpack notAPoint <> " is not a public key")++ -- The key is the type 0x01 and the 78 bytes of an extended key, 79 in+ -- all (0x4f): the version 0488b21e, depth 0, no parent, index 0, a chain+ -- code of zeros, and the public key+ it "refuses an extended public key whose key is not a point of the curve" $ do+ let xpubWith key = "4f01" <> "0488b21e" <> "00" <> "00000000" <> "00000000" <> T.replicate 32 "00" <> key <> "04d90c6a4f"+ (encodePsbt <$> decodePsbt (tinyPsbt (xpubWith aPoint) "" "")) `shouldBe` Right (tinyPsbt (xpubWith aPoint) "" "")+ decodePsbt (tinyPsbt (xpubWith notAPoint) "" "") `shouldSatisfy` either ("is not an extended public key" `isSuffixOf`) (const False)++ -- 0x3d is the 61 bytes of the transaction+ it "refuses a previous transaction that is not the one the input spends, in both versions" $ do+ decodePsbt (tinyPsbt "" ("01003d" <> anotherTx) "") `shouldBe` Left "Input 0: the previous transaction is not the one the input spends"+ decodePsbt (tinyPsbt2 ("01003d" <> anotherTx)) `shouldBe` Left "Input 0: the previous transaction is not the one the input spends"++ -- An empty tree, then a leaf of the leaf version 0xc1, a leaf at depth+ -- 129 (0x81), one leaf alone at depth 1, and three leaves at depth 1.+ -- 0x25 is the 37 bytes of a leaf, 0x6f the 111 bytes of three.+ it "refuses a script tree that is empty, has an odd leaf version, a leaf deeper than 128, or leaves that make no tree" $ do+ decodePsbt (tinyPsbt "" "" "010600") `shouldBe` Left "Output 0: the value of the key 0x06 is an empty script tree"+ decodePsbt (tinyPsbt "" "" ("010625" <> treeLeaf "00" "c1")) `shouldBe` Left "Output 0: the value of the key 0x06 has a leaf with an odd leaf version"+ decodePsbt (tinyPsbt "" "" ("010625" <> treeLeaf "81" "c0")) `shouldBe` Left "Output 0: the value of the key 0x06 has a leaf deeper than 128"+ decodePsbt (tinyPsbt "" "" ("010625" <> treeLeaf "01" "c0")) `shouldBe` Left "Output 0: the value of the key 0x06 is not a whole script tree"+ decodePsbt (tinyPsbt "" "" ("01066f" <> treeLeaf "01" "c0" <> treeLeaf "01" "c0" <> treeLeaf "01" "c0")) `shouldBe` Left "Output 0: the value of the key 0x06 is not a whole script tree"++ it "reads a script tree of one leaf at depth 0, and one of two leaves at depth 1" $ do+ (encodePsbt <$> decodePsbt (tinyPsbt "" "" ("010625" <> treeLeaf "00" "c0"))) `shouldBe` Right (tinyPsbt "" "" ("010625" <> treeLeaf "00" "c0"))+ (encodePsbt <$> decodePsbt (tinyPsbt "" "" ("01064a" <> treeLeaf "01" "c0" <> treeLeaf "01" "c0"))) `shouldBe` Right (tinyPsbt "" "" ("01064a" <> treeLeaf "01" "c0" <> treeLeaf "01" "c0"))++ -- The length 1 of the value "aa", over five bytes and over nine+ it "refuses a length written over five or nine bytes that is not in its shortest form" $ do+ decodePsbt (hexBytes ("70736274ff" <> emptyTxEntry <> "0120" <> "fe01000000" <> "aa" <> "00")) `shouldBe` Left "A length in the global map is not in its shortest form"+ decodePsbt (hexBytes ("70736274ff" <> emptyTxEntry <> "0120" <> "ff0100000000000000" <> "aa" <> "00")) `shouldBe` Left "A length in the global map is not in its shortest form"++ -- The key is the type 0x15 and a control block of 33 bytes plus 32 per+ -- node. 128 nodes make 4,130 bytes with the type (fd2210), and 129 make+ -- 4,162 (fd4210). The value is a script of 34 bytes and the leaf version.+ it "refuses a control block of 129 nodes, and reads one of 128" $ do+ let leafEntry nodes len = len <> "15" <> "c0" <> T.replicate 32 "22" <> T.replicate (32 * nodes) "33" <> "23" <> "20" <> T.replicate 32 "22" <> "ac" <> "c0"+ (B.length . encodePsbt <$> decodePsbt (tinyPsbt "" (leafEntry 128 "fd2210") "")) `shouldBe` Right (B.length (tinyPsbt "" (leafEntry 128 "fd2210") ""))+ decodePsbt (tinyPsbt "" (leafEntry 129 "fd4210") "") `shouldSatisfy` either ("is not a control block" `isSuffixOf`) (const False)++ -- An extended key of depth 0 has no path. The value is the fingerprint+ -- and the one index 0'.+ it "refuses an extended public key of depth 0 with a path of one index" $ do+ let xpub = "4f01" <> "0488b21e" <> "00" <> "00000000" <> "00000000" <> T.replicate 32 "00" <> aPoint <> "08d90c6a4f00000080"+ decodePsbt (tinyPsbt xpub "" "") `shouldSatisfy` either ("has a path that is not as deep as the key" `isSuffixOf`) (const False)++ -- Two entries with the same key: the type 0x20 and 1,000 bytes of 0xab,+ -- 1,001 in all (fde903)+ it "names at most 40 bytes of the data of a key in a refusal, then its length" $ do+ let entry = "fde903" <> "20" <> T.replicate 1000 "ab" <> "00"+ decodePsbt (tinyPsbt (entry <> entry) "" "")+ `shouldBe` Left ("The global map has the key 0x20 with the data " <> concat (replicate 40 "ab") <> "… (1000 bytes) twice")++{- What the signer and the finalizer sign, and what they leave alone -}++-- | A transaction that spends the two outputs of a previous transaction+-- paying the two scripts, as a PSBT with that previous transaction on both+-- inputs. Each output holds 2 BTC.+spendingTwo :: ByteString -> ByteString -> Psbt+spendingTwo scriptA scriptB = Psbt Version0 tx Map.empty [input, input] [Map.empty]+ where+ parent = Tx 1 [TxIn (OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1) "" 0xffffffff] [TxOut 200000000 scriptA, TxOut 200000000 scriptB] [] 0+ tx = Tx 2 [TxIn (OutPoint (txHash parent) 0) "" 0xfffffffe, TxIn (OutPoint (txHash parent) 1) "" 0xfffffffe] [TxOut 399990000 (hexBytes "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac")] [] 0+ input = insertNonWitnessUtxo parent Map.empty++signerScopeSpec :: Ctx -> Spec+signerScopeSpec ctx =+ describe "What the signer and the finalizer sign, and what they leave alone" $ do+ let secret = PrivateKey secretOne True+ public = derivePublicKey ctx secret+ otherPublic = compressedKey ctx secretTwo+ signer = keySigner ctx secretOne+ ownScript = marshal ctx (PayPKHash (addressHash (marshal ctx public)))+ otherScript = marshal ctx (PayPKHash (addressHash (marshal ctx otherPublic)))+ (psbt, _) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx public)))+ parent = fromJust (either (const Nothing) id (nonWitnessUtxo (inputAt 0 psbt)))+ coin = head parent.outputs+ signatureCounts = mapM (fmap length . partialSignatures ctx) . (.inputs)+ keyHex = show (encodeHex (marshal ctx public))++ -- BIP 32 gives a key a depth of one byte, so no key is 256 steps below+ -- another+ it "a signer built from an extended key holds no key for a record whose path has 256 steps" $ do+ let master = makeXPrvKey "PSBT signer test"+ tooDeep = Derivation (xPubFP ctx (deriveXPubKey ctx master)) (listToPath (replicate 256 0))+ (xPrvSigner ctx master Nothing).keyOfRecord public tooDeep `shouldBe` Nothing++ it "signing leaves unsigned an input whose record names the signer's key with a path of 256 steps" $ do+ let master = makeXPrvKey "PSBT signer test"+ tooDeep = Derivation (xPubFP ctx (deriveXPubKey ctx master)) (listToPath (replicate 256 0))+ withRecord = withInputs (insertInputDerivation ctx public tooDeep <$> psbt.inputs) psbt+ (signatureCounts =<< signPsbt btc ctx (xPrvSigner ctx master Nothing) withRecord) `shouldBe` Right [0]++ -- The second record names a key the coin does not pay. A signer that+ -- derives a key for every record can be made to work without end.+ it "asks the signer for the key of a record only when the script of the coin names that key" $ do+ let onlyOwn = signer {keyOfRecord = \asked _ -> if asked == public then Just secretOne else error "asked for the key of a record the coin does not name"}+ withRecords = withInputs ((recordOf ctx public . recordOf ctx otherPublic) <$> psbt.inputs) psbt+ (signatureCounts =<< signPsbt btc ctx onlyOwn withRecords) `shouldBe` Right [1]++ -- The coin pays the hash of key 2, and the record names key 1, which+ -- the signer holds. BIP 174: "If a Signer cannot sign a transaction, it+ -- must not add a Partial Signature."+ it "adds no signature for a key the script of the coin does not name, even when a derivation record names it" $ do+ let (other, _) = spendingPsbt ctx (PayPKHash (addressHash (marshal ctx otherPublic)))+ (signatureCounts =<< signPsbt btc ctx signer (withInputs (recordOf ctx public <$> other.inputs) other)) `shouldBe` Right [0]++ it "adds no signature to a P2WSH coin whose witness script is missing, nor to a P2SH coin whose redeem script is missing" $ do+ let multisig = forceRight (encodeOutput ctx (PayMulSig [public, otherPublic] 2))+ (p2wsh, _) = spendingPsbt ctx (forceRight (toP2WSH multisig))+ (p2sh, _) = spendingPsbt ctx (forceRight (toP2SH multisig))+ (signatureCounts =<< signPsbt btc ctx signer (withInputs (recordOf ctx public <$> p2wsh.inputs) p2wsh)) `shouldBe` Right [0]+ (signatureCounts =<< signPsbt btc ctx signer (withInputs (recordOf ctx public <$> p2sh.inputs) p2sh)) `shouldBe` Right [0]++ it "signs a P2SH-P2WPKH coin with its redeem script by the hash of its key, without a derivation record" $ do+ let redeem = forceRight (encodeOutput ctx (PayWitnessPKHash (addressHash (marshal ctx public))))+ (nested, _) = spendingPsbt ctx (forceRight (toP2SH redeem))+ (signatureCounts =<< signPsbt btc ctx signer (withInputs (insertInputRedeemScript redeem <$> nested.inputs) nested)) `shouldBe` Right [1]++ -- 0x41 is SIGHASH_ALL (0x01) with SIGHASH_FORKID (0x40)+ it "on the Bitcoin Cash networks, signs an input that asks for no sighash type with SIGHASH_ALL and the fork id flag" $+ forM_ [bch, bchTest, bchTest4, bchRegTest] $ \net ->+ (map (B.last . snd) <$> (partialSignatures ctx . inputAt 0 =<< signPsbt net ctx signer psbt)) `shouldBe` Right [0x41]++ -- Input 0 is a coin of key 1. Input 1 is a P2WSH coin of someone else,+ -- whose witness script is <key 2> OP_CHECKSIGVERIFY <144>+ -- OP_CHECKSEQUENCEVERIFY, which is no template the library knows.+ it "signs and finalizes its own input, and leaves alone an input whose witness script is not a standard template" $ do+ let timelocked = Script [opPushData (marshal ctx otherPublic), OP_CHECKSIGVERIFY, opPushData "\x90\x00", OP_CHECKSEQUENCEVERIFY]+ both = spendingTwo ownScript (marshal ctx (forceRight (toP2WSH timelocked)))+ withScript = withInputs [inputAt 0 both, insertInputWitnessScript timelocked (inputAt 1 both)] both+ signed <- expectRight (signPsbt btc ctx signer withScript)+ signatureCounts signed `shouldBe` Right [1, 0]+ inputAt 1 signed `shouldBe` inputAt 1 withScript+ final <- expectRight (finalizePsbt btc ctx signed)+ map (isJust . finalScriptSig) final.inputs `shouldBe` [True, False]+ inputAt 1 final `shouldBe` inputAt 1 withScript++ -- Input 1 is a P2PKH coin of key 2, known from its output alone. The+ -- signer could not sign it anyway. Bitcoin Core leaves such an input+ -- unsigned and signs the others.+ it "leaves alone a legacy coin known from its witness output only when it holds no key for it, and signs its own input" $ do+ let both = spendingTwo ownScript otherScript+ witnessOnly = withInputs [inputAt 0 both, insertWitnessUtxo (TxOut 200000000 otherScript) Map.empty] both+ (signatureCounts =<< signPsbt btc ctx signer witnessOnly) `shouldBe` Right [1, 0]++ -- Bitcoin Core refuses the whole PSBT too: a sighash type that the+ -- caller did not ask for is a mismatch on any input that has its coin+ it "refuses a PSBT in which another party's input asks for a sighash type the signer does not accept" $ do+ let both = spendingTwo ownScript otherScript+ asking = withInputs [inputAt 0 both, insertSigHashType (setAnyoneCanPay sigHashSingle) (inputAt 1 both)] both+ signPsbt btc ctx signer asking `shouldBe` Left "Input 1: the sighash type 131 is not one the signer accepts"++ it "refuses to sign a SegWit version 0 coin with an uncompressed key" $ do+ let uncompressed = PublicKey public.point False+ (p2wpkh, _) = spendingPsbt ctx (PayWitnessPKHash (addressHash (marshal ctx uncompressed)))+ signPsbt btc ctx signer p2wpkh `shouldBe` Left "Input 0: a SegWit version 0 coin may not be signed with an uncompressed key"++ -- The signature is a real one of key 1, on another coin. The finalizer+ -- must refuse it for the form of the key, whatever it signs.+ it "the finalizer refuses a signature of an uncompressed key on a SegWit version 0 coin" $ do+ let uncompressed = PublicKey public.point False+ (p2wpkh, _) = spendingPsbt ctx (PayWitnessPKHash (addressHash (marshal ctx uncompressed)))+ signed <- expectRight (signPsbt btc ctx signer psbt)+ signature <- case partialSignatures ctx (inputAt 0 signed) of+ Right [(_, sig)] -> return sig+ _ -> fail "one signature"+ finalizePsbt btc ctx (withInputs (insertPartialSignature ctx uncompressed signature <$> p2wpkh.inputs) p2wpkh)+ `shouldBe` Left ("Input 0: the signature of the key " <> show (encodeHex (marshal ctx uncompressed)) <> " is of an uncompressed key, which a SegWit version 0 coin may not be spent with")++ -- The second signature is the one of key 1, filed under key 2: it does+ -- not verify for key 2. The coin does not name key 2, so the finalizer+ -- has no reason to look at it.+ it "the finalizer does not look at a partial signature of a key the script of the coin does not name" $ do+ signed <- expectRight (signPsbt btc ctx signer psbt)+ signature <- case partialSignatures ctx (inputAt 0 signed) of+ Right [(_, sig)] -> return sig+ _ -> fail "one signature"+ let withStray = withInputs (insertPartialSignature ctx otherPublic signature <$> signed.inputs) signed+ (finalScriptSig . inputAt 0 <$> finalizePsbt btc ctx withStray) `shouldBe` (finalScriptSig . inputAt 0 <$> finalizePsbt btc ctx signed)+ (isJust . finalScriptSig . inputAt 0 <$> finalizePsbt btc ctx withStray) `shouldBe` Right True++ -- Bitcoin Core takes SIGHASH_ALL for an input that names no sighash+ -- type, and gives no final script when a signature uses another+ it "the finalizer refuses a SIGHASH_NONE signature on an input that names no sighash type" $ do+ let acceptingNone = signer {acceptedSigHashes = [sigHashNone]}+ signed <- expectRight (signPsbt btc ctx acceptingNone (withInputs (insertSigHashType sigHashNone <$> psbt.inputs) psbt))+ finalizePsbt btc ctx (withInputs (deleteSigHashType <$> signed.inputs) signed)+ `shouldBe` Left ("Input 0: the signature of the key " <> keyHex <> " does not use SIGHASH_ALL, and the input names no sighash type")++ it "the finalizer leaves alone a signed legacy coin that is known from its witness output only" $ do+ signed <- expectRight (signPsbt btc ctx signer psbt)+ let witnessOnly = withInputs [insertWitnessUtxo coin (Map.delete (Key 0x00 "") (inputAt 0 signed))] signed+ finalizePsbt btc ctx witnessOnly `shouldBe` Right witnessOnly++ -- A signer written by a caller may answer with a key that is not the+ -- one asked for. Its signature would be of no use to anyone.+ it "signs with a key the signer gives only when it is the key the coin names" $ do+ let wrongByRecord = signer {keyOfRecord = \_ _ -> Just secretTwo, keyOfHash = const Nothing}+ wrongByHash = signer {keyOfRecord = \_ _ -> Nothing, keyOfHash = const (Just (PrivateKey secretTwo True))}+ withRecord = withInputs (recordOf ctx public <$> psbt.inputs) psbt+ (signatureCounts =<< signPsbt btc ctx wrongByRecord withRecord) `shouldBe` Right [0]+ (signatureCounts =<< signPsbt btc ctx wrongByHash psbt) `shouldBe` Right [0]++ it "a signer of one key holds no key for the record of another key" $ do+ let record = Derivation (fingerprintOf "00000000") "m/0"+ (keySigner ctx secretOne).keyOfRecord otherPublic record `shouldBe` Nothing+ (keySigner ctx secretOne).keyOfRecord public record `shouldBe` Just secretOne++ -- The time lock 100 is below 500,000,000, so it would be read as a height+ it "a constructor refuses an input whose map breaks the spec, such as a required time lock below 500000000" $ do+ let modifiable = withFlags (Modifiable True True False 0) (withVersion Version2 (psbtOf (Tx 2 [] [] [] 0)))+ addInput coinInput (Map.singleton (Key 0x11 "") (hexBytes "64000000")) modifiable+ `shouldBe` Left "Input 0: the value of the key 0x11 is a time lock below 500000000"+ addOutput paidOutput (Map.singleton (Key 0x06 "") "") modifiable+ `shouldBe` Left "Output 0: the value of the key 0x06 is an empty script tree"+ -- The previous transaction has one output, and the input spends its second+ let previous = Tx 1 [coinInput] [paidOutput] [] 0+ addInput (TxIn (OutPoint (txHash previous) 1) "" 0xffffffff) (insertNonWitnessUtxo previous Map.empty) modifiable+ `shouldBe` Left "Input 0: the input spends an output its previous transaction does not have"+ addInput (TxIn (OutPoint (txHash previous) 0) "" 0xffffffff) (insertNonWitnessUtxo emptyTx Map.empty) modifiable+ `shouldBe` Left "Input 0: the previous transaction is not the one the input spends"++ -- 0065cd1d is 500,000,000, the first lock time that is a time, and+ -- ff64cd1d is 499,999,999+ it "a required time lock of 500000000 is the lock time of the transaction, and one of 499999999 is refused" $ do+ ((.unsignedTx.locktime) <$> decodePsbt (tinyPsbt2 "0111040065cd1d")) `shouldBe` Right 500000000+ decodePsbt (tinyPsbt2 "011104ff64cd1d") `shouldBe` Left "Input 0: the value of the key 0x11 is a time lock below 500000000"++{- The checks on Taproot signatures -}++-- | The PSBT with each of its input maps changed+onInputs :: (PsbtMap -> PsbtMap) -> Psbt -> Psbt+onInputs f psbt = withInputs (f <$> psbt.inputs) psbt++-- | The map with the entries of one key type changed+onKeyType :: Word8 -> (Key -> ByteString -> (Key, ByteString)) -> PsbtMap -> PsbtMap+onKeyType t f m = Map.fromList [if key.keyType == t then f key value else (key, value) | (key, value) <- Map.toList m]++-- | Bytes with their last bit flipped+flipLastBit :: ByteString -> ByteString+flipLastBit bytes = B.init bytes <> B.singleton (B.last bytes `xor` 1)++-- | An entry with the last bit of its value flipped, and one with the last+-- bit of the data of its key flipped+flipValue, flipKeyData :: Key -> ByteString -> (Key, ByteString)+flipValue key value = (key, flipLastBit value)+flipKeyData key value = (Key key.keyType (flipLastBit key.keyData), value)++taprootChecksSpec :: Ctx -> Vectors371 -> Spec+taprootChecksSpec ctx vectors =+ describe "The checks on Taproot signatures" $ do+ let vectorAt i = psbtOfVector (vectors.accepted371 !! i)+ internal = derivePubKey ctx secretOne+ leafKey = derivePubKey ctx secretTwo+ leaf = Script [opPushData (exportXOnlyPubKey ctx leafKey), OP_CHECKSIG]+ tree = MASTBranch (MASTLeaf 0xc0 leaf) (MASTLeaf 0xc0 (Script [OP_RETURN]))+ output = TaprootOutput internal (Just tree)+ control = encodeControlBlock ctx (0xc0 + keyParity ctx (taprootOutputKey ctx output)) internal [leafHash 0xc0 (Script [OP_RETURN])]+ (withTree, _) = spendingPsbt ctx (taprootScriptOutput ctx output)+ treeFields = insertInputTapInternalKey ctx internal . insertInputTapMerkleRoot (mastCommitment tree) . insertTapLeafScript (TapLeafScript control leaf 0xc0)+ derivation = Derivation (fingerprintOf "d90c6a4f") "m/86'/0'/0'/0/0"+ -- A signer that finds its keys by derivation record only, as one+ -- built from an extended key does+ byRecord secret = (keySigner ctx secret) {keyOfHash = const Nothing}++ it "the finalizer refuses a key path signature that does not verify" $ do+ psbt <- vectorAt 1+ finalizePsbt btc ctx (onInputs (onKeyType 0x13 flipValue) psbt)+ `shouldBe` Left "Input 0: the key path signature does not verify"++ it "the finalizer refuses a script path signature that does not verify" $ do+ psbt <- vectorAt 5+ finalizePsbt btc ctx (onInputs (onKeyType 0x14 flipValue) psbt)+ `shouldBe` Left "Input 0: the script path signature does not verify"++ it "the finalizer refuses a leaf whose control block does not commit it to the output key" $ do+ psbt <- vectorAt 5+ finalizePsbt btc ctx (onInputs (onKeyType 0x15 flipKeyData) psbt)+ `shouldBe` Left "Input 0: the control block of the leaf does not commit it to the output key"++ -- The signatures of both vectors are 64 bytes, which is SIGHASH_DEFAULT+ it "the finalizer refuses a Taproot signature that does not use the sighash type the input names" $ do+ keyPath <- vectorAt 1+ scriptPath <- vectorAt 5+ finalizePsbt btc ctx (onInputs (insertSigHashType sigHashAll) keyPath) `shouldBe` Left "Input 0: a Taproot signature does not use the sighash type of the input"+ finalizePsbt btc ctx (onInputs (insertSigHashType sigHashAll) scriptPath) `shouldBe` Left "Input 0: a Taproot signature does not use the sighash type of the input"++ -- Bitcoin Core takes SIGHASH_DEFAULT for a Taproot input that names no+ -- sighash type, and then wants every signature to be 64 bytes+ it "the finalizer refuses a SIGHASH_ALL key path signature on an input that names no sighash type" $ do+ let (keyOnly, _) = spendingPsbt ctx (taprootScriptOutput ctx (TaprootOutput internal Nothing))+ asking = onInputs (insertSigHashType sigHashAll . insertInputTapInternalKey ctx internal) keyOnly+ signed <- expectRight (signPsbt btc ctx (keySigner ctx secretOne) asking)+ (fmap (fmap (.hash)) . tapKeySig . inputAt 0) signed `shouldBe` Right (Just sigHashAll)+ finalizePsbt btc ctx (onInputs deleteSigHashType signed)+ `shouldBe` Left "Input 0: a Taproot signature does not use SIGHASH_DEFAULT, and the input names no sighash type"++ it "the signer refuses a Taproot coin that asks for a sighash type the signer does not accept" $ do+ let (keyOnly, _) = spendingPsbt ctx (taprootScriptOutput ctx (TaprootOutput internal Nothing))+ signPsbt btc ctx (keySigner ctx secretOne) (onInputs (insertSigHashType sigHashSingle . insertInputTapInternalKey ctx internal) keyOnly)+ `shouldBe` Left "Input 0: the sighash type 3 is not one the signer accepts"++ -- BIP 371: the leaf hashes of a record "are of the leaves which involve+ -- this public key". A key that is the internal key and also a leaf key+ -- has a record with a leaf hash, and Bitcoin Core writes it that way.+ it "signs the key path of a Taproot coin when the record of its internal key lists a leaf" $ do+ let record = insertInputTapDerivation ctx internal (TapDerivation [leafHash 0xc0 leaf] derivation)+ signed <- expectRight (signPsbt btc ctx (byRecord secretOne) (onInputs (record . treeFields) withTree))+ (fmap isJust . tapKeySig . inputAt 0) signed `shouldBe` Right True++ it "signs a leaf with the key of a record that lists that leaf, and not with one that lists another leaf" $ do+ let listing hashes = insertInputTapDerivation ctx leafKey (TapDerivation hashes derivation)+ signatures f = fmap length . tapScriptSigs ctx . inputAt 0 =<< signPsbt btc ctx (byRecord secretTwo) (onInputs (f . treeFields) withTree)+ signatures (listing [leafHash 0xc0 leaf]) `shouldBe` Right 1+ signatures (listing [leafHash 0xc0 (Script [OP_RETURN])]) `shouldBe` Right 0++ -- The leaf is in no tree of the coin: its control block names another+ -- internal key+ it "signs no leaf whose control block does not commit it to the coin" $ do+ let stray = encodeControlBlock ctx 0xc0 leafKey [leafHash 0xc0 (Script [OP_RETURN])]+ fields = insertInputTapInternalKey ctx internal . insertInputTapMerkleRoot (mastCommitment tree) . insertTapLeafScript (TapLeafScript stray leaf 0xc0)+ signed <- expectRight (signPsbt btc ctx (keySigner ctx secretTwo) (onInputs fields withTree))+ (fmap length . tapScriptSigs ctx . inputAt 0) signed `shouldBe` Right 0++ it "signs a Taproot coin of a PSBT of version 2 by its key path, and the PSBT stays of version 2" $ do+ let (keyOnly, coins) = spendingPsbt ctx (taprootScriptOutput ctx (TaprootOutput internal Nothing))+ version2 = withVersion Version2 (onInputs (insertInputTapInternalKey ctx internal) keyOnly)+ final <- expectRight (finalizePsbt btc ctx =<< signPsbt btc ctx (keySigner ctx secretOne) version2)+ final.version `shouldBe` Version2+ tx <- expectRight (psbtTransaction final)+ verifyStdTx btc ctx tx coins `shouldBe` True++{- The coins of a PSBT, and the previous transaction a signer may require -}++coinsSpec :: Ctx -> Spec+coinsSpec ctx =+ describe "The coins of a PSBT" $ do+ let public = compressedKey ctx secretOne+ signer = keySigner ctx secretOne+ program = PayWitnessPKHash (addressHash (marshal ctx public))+ (psbt, _) = spendingPsbt ctx program+ parent = fromJust (either (const Nothing) id (nonWitnessUtxo (inputAt 0 psbt)))+ output = head parent.outputs+ witnessOnly = withInputs [insertWitnessUtxo output Map.empty] psbt+ signatureCounts = mapM (fmap length . partialSignatures ctx) . (.inputs)++ it "tells the coin of each input, and whether it comes from the previous transaction of the input" $ do+ inputCoins psbt `shouldBe` Right [Just (InputCoin output True)]+ inputCoins witnessOnly `shouldBe` Right [Just (InputCoin output False)]+ inputCoins (withInputs [Map.empty] psbt) `shouldBe` Right [Nothing]++ -- The coin holds 200,000,000 satoshi and the outputs pay 99,999,699 and+ -- 100,000,000: 200,000,000 − 199,999,699 = 301+ it "gives the fee when every coin comes from its previous transaction" $+ psbtFee psbt `shouldBe` Right 301++ it "gives no fee when the amount of a coin is only a claim, when a coin is missing, or when the outputs pay more than the coins hold" $ do+ psbtFee witnessOnly `shouldBe` Left "Input 0: the coin is given as a witness output only, so its amount is not checked"+ psbtFee (withInputs [Map.empty] psbt) `shouldBe` Left "Input 0 has no coin"+ -- The same transaction with one more output of 302 satoshi+ let tx = psbt.unsignedTx+ paysMore = Psbt Version0 (Tx tx.version tx.inputs (tx.outputs <> [TxOut 302 ""]) [] tx.locktime) Map.empty psbt.inputs [Map.empty, Map.empty, Map.empty]+ psbtFee paysMore `shouldBe` Left "The outputs pay more than the coins hold"++ -- BIP 174: "The Signer may choose to fail to sign a segwit input if a+ -- non-witness UTXO is not provided."+ it "a signer refuses a SegWit version 0 coin known from its witness output only, and signs it with its previous transaction" $ do+ signPsbt btc ctx signer witnessOnly+ `shouldBe` Left "Input 0: the coin is given as a witness output only, and the signer requires the previous transaction of a SegWit version 0 coin"+ (signatureCounts =<< signPsbt btc ctx signer psbt) `shouldBe` Right [1]+ (signatureCounts =<< signPsbt btc ctx signer (withInputs [insertWitnessUtxo output (inputAt 0 psbt)] psbt)) `shouldBe` Right [1]++ it "a signer told to trust witness outputs signs a SegWit version 0 coin known from its witness output only" $+ (signatureCounts =<< signPsbt btc ctx (signer {requirePreviousTx = False}) witnessOnly) `shouldBe` Right [1]++ -- CVE-2020-14199. The signer holds two coins of 15 and 20 BTC. The+ -- writer of the PSBT understates the first as 5 BTC and overstates the+ -- second. A signature of each input commits to the amount of that input+ -- alone, so the signature of the second input would be valid in a+ -- transaction that burns the 10 BTC of difference as fee.+ it "a signer refuses a PSBT that states the amounts of its SegWit version 0 coins without their previous transactions" $ do+ let both = spendingTwo (marshal ctx program) (marshal ctx program)+ claimed = withInputs [insertWitnessUtxo (TxOut 500000000 (marshal ctx program)) Map.empty, insertWitnessUtxo (TxOut 2000000000 (marshal ctx program)) Map.empty] both+ signPsbt btc ctx signer claimed+ `shouldBe` Left "Input 0: the coin is given as a witness output only, and the signer requires the previous transaction of a SegWit version 0 coin"++ -- A Taproot signature commits to the amount of every coin (BIP 341)+ it "a signer signs a Taproot coin known from its witness output only" $ do+ let internal = derivePubKey ctx secretOne+ (taproot, _) = spendingPsbt ctx (taprootScriptOutput ctx (TaprootOutput internal Nothing))+ (fmap isJust . tapKeySig . inputAt 0 =<< signPsbt btc ctx signer (withInputs (insertInputTapInternalKey ctx internal <$> taproot.inputs) taproot)) `shouldBe` Right True++{- The fields of version 2, read one by one -}++version2FieldsSpec :: Spec+version2FieldsSpec =+ describe "The lock time fields of a PSBT of version 2" $ do+ -- 0x03 is the fallback lock time of the global map, 0x11 and 0x12 the+ -- lock times an input requires as a time and as a height+ it "read the fallback lock time of the global map, when it has one" $ do+ fallbackLockTime (Map.singleton (Key 0x03 "") (hexBytes "10270000")) `shouldBe` Right (Just 10000)+ fallbackLockTime Map.empty `shouldBe` Right Nothing+ fallbackLockTime (Map.singleton (Key 0x03 "") (hexBytes "1027")) `shouldSatisfy` either ("is not a 32-bit integer" `isSuffixOf`) (const False)++ it "read the lock times an input requires, as a time and as a height" $ do+ requiredTimeLockTime (Map.singleton (Key 0x11 "") (hexBytes "0065cd1d")) `shouldBe` Right (Just 500000000)+ requiredHeightLockTime (Map.singleton (Key 0x12 "") (hexBytes "10270000")) `shouldBe` Right (Just 10000)+ requiredTimeLockTime Map.empty `shouldBe` Right Nothing+ requiredHeightLockTime Map.empty `shouldBe` Right Nothing++ -- The global map of version 2 with no input and no output: the+ -- transaction version 2, the two counts of 0, and the PSBT version 2+ it "build a PSBT from its maps, and refuse maps that are not as many as the global map counts" $ do+ let globals = Map.fromList [(Key 0x02 "", hexBytes "02000000"), (Key 0x04 "", hexBytes "00"), (Key 0x05 "", hexBytes "00"), (Key 0xfb "", hexBytes "02000000")]+ (encodeHex . encodePsbt <$> psbtOfMaps globals [] []) `shouldBe` Right "70736274ff01020402000000010401000105010001fb040200000000"+ psbtOfMaps globals [Map.empty] [] `shouldBe` Left "The PSBT has 1 inputs, and its global map counts 0"+ psbtOfMaps globals [] [Map.empty] `shouldBe` Left "The PSBT has 1 outputs, and its global map counts 0"
test/Haskoin/Transaction/TaprootSpec.hs view
@@ -4,6 +4,8 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-} -- | Taproot: the script trees, output keys and control blocks of the BIP -- 341 vectors, key path spending with its signature digest, the witness of@@ -11,21 +13,23 @@ module Haskoin.Transaction.TaprootSpec (spec) where import Control.Applicative ((<|>))-import Control.Monad (forM, forM_, zipWithM_, (<=<))-import Crypto.Hash (Digest, SHA256, digestFromByteString)+import Control.Monad (forM, forM_, (<=<))+import Crypto.Hash (Digest, SHA256, digestFromByteString, hashFinalize, hashUpdate) import Data.Aeson import Data.Aeson.Types import Data.ByteArray qualified as BA import Data.ByteString (ByteString) import Data.ByteString qualified as BS import Data.Bytes.Get (runGetS)-import Data.Bytes.Put (runPutS)+import Data.Bytes.Put (putByteString, putWord8, runPutS) import Data.Bytes.Serial (deserialize, serialize)+import Data.Bytes.VarInt qualified as Base128 import Data.Either (isLeft) import Data.Maybe (fromJust, isJust, isNothing) import Data.Serialize qualified as S import Data.Text (Text)-import Data.Word (Word32, Word64)+import Data.Text qualified+import Data.Word (Word32, Word64, Word8) import Haskoin import Haskoin.Util.Arbitrary import Test.HUnit (assertBool, (@?=))@@ -76,7 +80,7 @@ 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+ (addrToText btc =<< outputAddress btc 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 @@ -93,7 +97,7 @@ 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+ fmap (encodeHex . encodeTaprootSig) (taprootSignature (signSchnorr ctx tweaked (hashToMsg digest)) c.hashType) `shouldBe` Right (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@@ -115,6 +119,14 @@ forM_ noTree $ \c -> (signed.witness !! c.index) `shouldBe` [hexBytes (head c.witness)] + -- The witness of each input is in the vector, so this checks the+ -- signer against the BIP for all seven inputs and hash types+ it "signTaprootInput signs each Taproot input of the vector with its output secret key, and gives the witness of the vector" $+ forM_ testKeyPath.cases $ \c -> do+ let tweaked = fromJust (taprootOutputSecKey ctx c.internalSecret (MASTCommitment <$> c.merkleRoot))+ signed <- expectRight (signTaprootInput ctx testKeyPath.unsignedTx testKeyPath.spent c.index c.hashType tweaked)+ (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@@ -138,12 +150,29 @@ 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)+ fmap encodeTaprootSig (taprootSignature sig sigHashDefault) `shouldBe` Right (BS.replicate 64 7)+ fmap encodeTaprootSig (taprootSignature sig sigHashAll) `shouldBe` Right (BS.replicate 64 7 <> "\x01")+ fmap encodeTaprootSig (taprootSignature sig (setAnyoneCanPay sigHashSingle)) `shouldBe` Right (BS.replicate 64 7 <> "\x83")+ fmap (\s -> (s.sig, s.hash)) (decodeTaprootSig (BS.replicate 64 7)) `shouldBe` Right (sig, sigHashDefault)+ fmap (\s -> (s.sig, s.hash)) (decodeTaprootSig (BS.replicate 64 7 <> "\x02")) `shouldBe` Right (sig, sigHashNone) + it "are not built with a hash type that Taproot does not define" $ do+ let sig = fromJust (schnorrSig (BS.replicate 64 7))+ taprootSignature sig 0x04 `shouldBe` Left "taprootSignature: invalid hash type 4"+ taprootSignature sig 0x41 `shouldBe` Left "taprootSignature: invalid hash type 65"+ taprootSignature sig 0x80 `shouldBe` Left "taprootSignature: invalid hash type 128"+ -- Cut to one byte, 0x100 would be a 65-byte signature ending in 00,+ -- which BIP 341 forbids, and 0x101 would say SIGHASH_ALL+ taprootSignature sig 0x100 `shouldBe` Left "taprootSignature: invalid hash type 256"+ taprootSignature sig 0x101 `shouldBe` Left "taprootSignature: invalid hash type 257"++ it "of each of the seven hash types are 64 or 65 bytes that read back as the same signature" $ do+ let sig = fromJust (schnorrSig (BS.replicate 64 7))+ forM_ [(0x00, 64), (0x01, 65), (0x02, 65), (0x03, 65), (0x81, 65), (0x82, 65), (0x83, 65)] $ \(hashType, size) -> do+ signature <- expectRight (taprootSignature sig hashType)+ BS.length (encodeTaprootSig signature) `shouldBe` size+ decodeTaprootSig (encodeTaprootSig signature) `shouldBe` Right signature+ 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"@@ -164,13 +193,13 @@ 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}+ spend = ScriptPathData {annex = Nothing, stack = ["aa", "bbbb"], script = leaf, extIsOdd = odd (keyParity ctx outputKey), leafVersion = tapscriptLeafVersion, internalKey, control = proof, opaqueScript = Nothing} 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"]+ encodeTaprootWitness ctx (KeyPathSpend "sig" Nothing) `shouldBe` Right ["sig"]+ encodeTaprootWitness ctx (KeyPathSpend "sig" (Just "\x50\&annex")) `shouldBe` Right ["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@@ -178,8 +207,8 @@ 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"]+ encodeTaprootWitness ctx (ScriptPathSpend spend) `shouldBe` Right ["aa", "bbbb", scriptBytes, control]+ encodeTaprootWitness ctx (ScriptPathSpend spend {annex = Just "\x50"}) `shouldBe` Right ["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 = []})@@ -187,6 +216,42 @@ -- spend: the script is not a control block viewTaprootWitness ctx [control, scriptBytes] `shouldBe` Nothing + -- BIP 342: a Tapscript that holds an OP_SUCCESSx opcode succeeds, "even+ -- if later bytes in the tapscript would fail to decode otherwise". 50 is+ -- OP_SUCCESS80, and 4c is an OP_PUSHDATA1 with no length after it.+ it "a script path spend is read whatever the bytes of its script are" $ do+ let opaque = "\x50\x4c"+ opaqueControl = encodeControlBlock ctx 0xc0 internalKey []+ expected = ScriptPathData {annex = Nothing, stack = [], script = Script [], extIsOdd = False, leafVersion = 0xc0, internalKey, control = [], opaqueScript = Just opaque}+ viewTaprootWitness ctx [opaque, opaqueControl] `shouldBe` Just (ScriptPathSpend expected)+ scriptPathScriptBytes expected `shouldBe` opaque+ encodeTaprootWitness ctx (ScriptPathSpend expected) `shouldBe` Right [opaque, opaqueControl]+ -- A leaf of another version, whose bytes are a push cut short+ fmap (\case ScriptPathSpend d -> (d.leafVersion, d.opaqueScript); _ -> (0, Nothing)) (viewTaprootWitness ctx ["\x02\xaa", encodeControlBlock ctx 0xc2 internalKey []])+ `shouldBe` Just (0xc2, Just "\x02\xaa")++ it "a script path spend of bytes that are not opcodes verifies against the output key that commits to those bytes" $ do+ let opaque = "\x50\x4c"+ key = taprootOutputKey ctx (TaprootOutput internalKey (Just (MASTCommitment (leafHashBytes 0xc0 opaque))))+ opaqueSpend = ScriptPathData {annex = Nothing, stack = [], script = Script [], extIsOdd = odd (keyParity ctx key), leafVersion = 0xc0, internalKey, control = [], opaqueScript = Just opaque}+ verifyScriptPathData ctx key opaqueSpend `shouldBe` True+ verifyScriptPathData ctx key opaqueSpend {opaqueScript = Just "\x50\x4d"} `shouldBe` False++ it "a witness is not written with an annex that is empty or does not start with 0x50" $ do+ encodeTaprootWitness ctx (KeyPathSpend "sig" (Just "hello")) `shouldBe` Left "encodeTaprootWitness: an annex starts with 0x50"+ encodeTaprootWitness ctx (KeyPathSpend "sig" (Just "")) `shouldBe` Left "encodeTaprootWitness: an annex starts with 0x50"+ encodeTaprootWitness ctx (ScriptPathSpend spend {annex = Just "hello"}) `shouldBe` Left "encodeTaprootWitness: an annex starts with 0x50"++ it "a witness is written with a proof of 128 hashes, and not with one of 129" $ do+ let node = fromJust (digestFromByteString @SHA256 (BS.replicate 32 1))+ -- two stack items, the script and the control block+ fmap length (encodeTaprootWitness ctx (ScriptPathSpend spend {control = replicate 128 node})) `shouldBe` Right 4+ encodeTaprootWitness ctx (ScriptPathSpend spend {control = replicate 129 node}) `shouldBe` Left "encodeTaprootWitness: a control block holds at most 128 nodes"++ it "a witness is not written with a leaf version that a control block cannot carry" $ do+ encodeTaprootWitness ctx (ScriptPathSpend spend {leafVersion = 0xc1}) `shouldBe` Left "encodeTaprootWitness: a control block cannot carry the leaf version 193"+ encodeTaprootWitness ctx (ScriptPathSpend spend {leafVersion = 0x50}) `shouldBe` Left "encodeTaprootWitness: a control block cannot carry the leaf version 80"+ 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@@ -219,7 +284,8 @@ 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)))+ viewed witness = viewTaprootWitness ctx =<< eitherToMaybe (encodeTaprootWitness ctx witness)+ in (viewed (KeyPathSpend sig annex), viewed (spendWith annex)) === (Just (KeyPathSpend sig annex), Just (spendWith annex)) describe "Script trees as their leaves" $ do@@ -241,7 +307,6 @@ 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@@ -258,6 +323,262 @@ it "the Tapscript leaf version is 0xc0" $ tapscriptLeafVersion `shouldBe` 0xc0 + describe "Leaf hashes" $ do+ -- BIP 341: the tapleaf hash is hash_TapLeaf(v || compact_size(size of s) || s).+ -- The literals were computed from that sentence with Python's hashlib:+ -- SHA256(SHA256("TapLeaf") || SHA256("TapLeaf") || c0 || length || script),+ -- for a script of n bytes 0x61 (OP_NOP).+ it "of a script of 127 bytes, whose length is written 7f" $+ digestHex (leafHash 0xc0 (Script (replicate 127 OP_NOP))) `shouldBe` "768beb2ec05d13f86bef3b81dda9ce0b4f22d82a6a5ec5ab5a5f2ca6da6567d0"+ it "of a script of 128 bytes, whose length is written 80" $+ digestHex (leafHash 0xc0 (Script (replicate 128 OP_NOP))) `shouldBe` "8c247c24a8b5def0f3cc6fe79098bf761275c4c4e1b27739d6b5caf7fabe0ef7"+ it "of a script of 252 bytes, whose length is written fc" $+ digestHex (leafHash 0xc0 (Script (replicate 252 OP_NOP))) `shouldBe` "bed46d76590727c80034f9c383ca18e25e1c71ede72a134ccf772d2852747408"+ it "of a script of 253 bytes, whose length is written fd fd 00" $+ digestHex (leafHash 0xc0 (Script (replicate 253 OP_NOP))) `shouldBe` "03a32feb39d860fbc91750ba7ad075ce987a0b1a86828115748e07e32e3dfc5d"++ -- tr(cba52b43…6970,multi_a(2,b2fff6fd…,53f8149c…,a07f1b7d…,b7e3bfb9…)).+ -- Bitcoin Core 32.1 gives this leaf hash and this output script+ -- (decodepsbt and deriveaddresses on regtest). The leaf is 138 bytes:+ -- 4 keys × 34 bytes + OP_2 + OP_NUMEQUAL.+ it "of the 138-byte multi_a leaf is the one Bitcoin Core derives, and so is the output script" $ do+ let leaf = scriptOf multiALeaf+ internal = fromJust (importXOnlyPubKey ctx (hexBytes "cba52b4389f798ec8ba66d9520ce532af2fe642b970010e2fbd0ec36bce36970"))+ BS.length (hexBytes multiALeaf) `shouldBe` 138+ digestHex (leafHash 0xc0 leaf) `shouldBe` "c6831caa9b274f379336d6bcc789aa6e82760d573e3750b97df37e624d8e6c90"+ encodeHex (marshal ctx (taprootScriptOutput ctx (TaprootOutput internal (Just (MASTLeaf 0xc0 leaf)))))+ `shouldBe` "51207fd815afed80f6bc581ac94aa52d85c50d2bddff7347e1094936eb71e2f18936"++ it "of script bytes that are not a list of opcodes is taken on the bytes" $+ -- 50 4c is OP_SUCCESS80 followed by an OP_PUSHDATA1 with no length.+ -- SHA256(tag || tag || c0 || 02 || 50 4c), computed with Python's hashlib.+ digestHex (leafHashBytes 0xc0 "\x50\x4c") `shouldBe` "070bb467edca609e1a3246a8ba31e6266509d0d4e74e5fb2a32359b5f1565272"++ -- Versions up to 1.5.1 wrote the script length of a leaf as a base-128+ -- varint. For a leaf of 128 bytes or more that gave another leaf hash,+ -- and so another output key. Coins sent to such an output are still+ -- reachable: the old leaf hash goes in the tree as a commitment. The old+ -- values below were printed by the unfixed library and recomputed in+ -- Python with the base-128 length.+ describe "An output made by versions up to 1.5.1 with a leaf of 128 bytes or more" $ do+ let internalSecret = fromJust (secKey (hexBytes "01c58048b9108d028cdde54d8ea8fa545cd4145157fad6e5a908809c7e35388d"))+ internal = derivePubKey ctx internalSecret+ oldLeafHash = leafHashBefore152 0xc0 (scriptOf multiALeaf)+ xOnly = encodeHex . exportXOnlyPubKey ctx+ it "has a leaf hash that is rebuilt with the base-128 length" $ do+ xOnly internal `shouldBe` "cba52b4389f798ec8ba66d9520ce532af2fe642b970010e2fbd0ec36bce36970"+ digestHex oldLeafHash `shouldBe` "4a0b2a93f8430729d9467c0bc91d26d54b63802e627783bf808694c8e8dbc372"+ it "has the output key of the tree that holds the old leaf hash as a commitment" $+ xOnly (taprootOutputKey ctx (TaprootOutput internal (Just (MASTCommitment oldLeafHash))))+ `shouldBe` "15e87fb4cb019af07cf16fa2b9a8451536bd86321dcc11053c70d9038ec97c80"+ it "is spent by its key path with the secret key tweaked with that commitment" $+ fmap (xOnly . derivePubKey ctx) (taprootOutputSecKey ctx internalSecret (Just (MASTCommitment oldLeafHash)))+ `shouldBe` Just "15e87fb4cb019af07cf16fa2b9a8451536bd86321dcc11053c70d9038ec97c80"+ it "keeps the script path of a short leaf next to the long one" $ do+ -- The tree {pk(1cf7198d…2500), multi_a leaf}+ let short = Script [opPushData (hexBytes "1cf7198d10d0552c8df4d0380372a5fa224a1226fc3a49546a416086442d2500"), OP_CHECKSIG]+ oldTree = MASTBranch (MASTLeaf 0xc0 short) (MASTCommitment oldLeafHash)+ oldKey = taprootOutputKey ctx (TaprootOutput internal (Just oldTree))+ proofs = [proof | (_, _, proof) <- getMerkleProofs oldTree]+ spendOf proof = ScriptPathData {annex = Nothing, stack = [], script = short, extIsOdd = odd (keyParity ctx oldKey), leafVersion = 0xc0, internalKey = internal, control = proof, opaqueScript = Nothing}+ xOnly oldKey `shouldBe` "120ed04aacce013f8ed3b63a53143aec81a4c05fa0a263a693d14aa8bebd7efd"+ map (map digestHex) proofs `shouldBe` [["4a0b2a93f8430729d9467c0bc91d26d54b63802e627783bf808694c8e8dbc372"]]+ map (verifyScriptPathData ctx oldKey . spendOf) proofs `shouldBe` [True]+ it "is not the output of the same tree today" $ do+ let short = Script [opPushData (hexBytes "1cf7198d10d0552c8df4d0380372a5fa224a1226fc3a49546a416086442d2500"), OP_CHECKSIG]+ tree = MASTBranch (MASTLeaf 0xc0 short) (MASTLeaf 0xc0 (scriptOf multiALeaf))+ -- Root and key computed in Python from the text of BIP 341+ digestHex (mastCommitment tree) `shouldBe` "4bff8cf0bd46ff4d4470c11a7f407dfee7078c7675b877691310c0ee0058c620"+ xOnly (taprootOutputKey ctx (TaprootOutput internal (Just tree))) `shouldBe` "b672ffdc20bf79cf4bda88ec7a0ad9c53c91e032f2bc169af425eae9dac8afe0"++ describe "Merkle proofs" $ do+ -- tr(ecd5eddb…7670,{pk(A),{pk(B),{pk(C),pk(D)}}}). The Merkle root and+ -- the four control blocks are what Bitcoin Core 32.1 prints in+ -- decodepsbt for this descriptor. A proof lists the siblings from the+ -- leaf up to the root, as taproot_tree_helper does in BIP 341.+ it "of a tree of depth 3 give each leaf the control block Bitcoin Core gives it" $ do+ let pkLeaf key = MASTLeaf 0xc0 (Script [opPushData (hexBytes key), OP_CHECKSIG])+ a = pkLeaf "1cf7198d10d0552c8df4d0380372a5fa224a1226fc3a49546a416086442d2500"+ b = pkLeaf "d1a2717d14f4798094719c904372393797b88d365766d95a021333fe3e6a526c"+ c = pkLeaf "5807c60bab1f93b36747e3d120c29fa898ab37e616b3b7635b21e5a71888e6da"+ d = pkLeaf "33045c7755fc6aef51859ad00d7393f1d23ff8b0bd6acf0d6c6d7a72502d905a"+ tree = MASTBranch a (MASTBranch b (MASTBranch c d))+ internal = fromJust (importXOnlyPubKey ctx (hexBytes "ecd5eddb647ff233131e292cef048228bce3a3b045319d563b57d59e294c7670"))+ outputKey = taprootOutputKey ctx (TaprootOutput internal (Just tree))+ controlBlocks = [encodeHex (encodeControlBlock ctx (0xc0 + keyParity ctx outputKey) internal proof) | (_, _, proof) <- getMerkleProofs tree]+ digestHex (mastCommitment tree) `shouldBe` "e0f4dd44e6affb70c64661b32d78666ce2296fcdf9abdcbbe9482e91398f4ac5"+ controlBlocks+ `shouldBe` [ "c1ecd5eddb647ff233131e292cef048228bce3a3b045319d563b57d59e294c7670cd8b2619baff8006c3b4f8d86a79d76d2a636b226a43602aa7b17908152a2db6",+ "c1ecd5eddb647ff233131e292cef048228bce3a3b045319d563b57d59e294c767096de41a8ea89de5ab00d7fe07ca5ef19dc4877e8869b08d70228add11bb98965c0243dd79cee2b5ec8ac1e254869b9a9a0a4a40a684a13e28ec8ef013588a1f3",+ "c1ecd5eddb647ff233131e292cef048228bce3a3b045319d563b57d59e294c76701995a0d81bdbda264923687c3593a67e5cba9b2adab467777885a6177368f3dbd864c47073b2a8367b8943dde8f76cd8856a071faf86f3bfb2680c6adc1fbddec0243dd79cee2b5ec8ac1e254869b9a9a0a4a40a684a13e28ec8ef013588a1f3",+ "c1ecd5eddb647ff233131e292cef048228bce3a3b045319d563b57d59e294c7670a6f339703d444033227bbd073048f0821f6c31b9441d7b986af598bdaf3a5c77d864c47073b2a8367b8943dde8f76cd8856a071faf86f3bfb2680c6adc1fbddec0243dd79cee2b5ec8ac1e254869b9a9a0a4a40a684a13e28ec8ef013588a1f3"+ ]++ describe "Any script tree" $ do+ prop "has the Merkle root and the proofs that taproot_tree_helper of BIP 341 gives" $+ forAll (arbitraryMAST True) $ \tree ->+ let (paths, root) = referenceTree tree+ in ( BA.convert (mastCommitment tree) :: ByteString,+ [(runPutS (serialize script), map BA.convert proof) | (_, script, proof) <- getMerkleProofs tree]+ )+ === (root, paths)+ prop "gives each leaf a proof of as many hashes as the leaf is deep, which verifies against the output key" $+ forAll (arbitraryMAST True) $ \tree ->+ let (_, p) = bip86Key ctx+ internal = fromJust (importXOnlyPubKey ctx (exportXOnlyPubKey ctx p.point))+ outputKey = taprootOutputKey ctx (TaprootOutput internal (Just tree))+ spendOf (version, script, proof) = ScriptPathData {annex = Nothing, stack = [], script, extIsOdd = odd (keyParity ctx outputKey), leafVersion = version, internalKey = internal, control = proof, opaqueScript = Nothing}+ proofs = getMerkleProofs tree+ in (map (\(_, _, proof) -> length proof) proofs, map (verifyScriptPathData ctx outputKey . spendOf) proofs)+ === (leafDepths tree, map (const True) proofs)+ prop "without a commitment is built back from the list of its leaves" $+ forAll (arbitraryMAST False) $ \tree ->+ fmap mastCommitment (tapTreeFromLeaves =<< tapTreeLeaves tree) === Just (mastCommitment tree)+ prop "lists its leaves with the depths they have" $+ forAll (arbitraryMAST False) $ \tree ->+ fmap (map (fromIntegral . (.depth))) (tapTreeLeaves tree) === Just (leafDepths tree)++ describe "Script path spends from Bitcoin mainnet" $ do+ spends <- runIO (readTestFileParser mainnetSpendsParseJSON "taproot_mainnet_spends.json")+ -- Each spend of the file is in a mainnet block, so its script and its+ -- control block do commit to the output key of the coin it spends.+ it "are read, and each script and control block commit to the output key of the coin spent" $ do+ length spends `shouldBe` 24+ let verdict (txid, spentScript, witness) =+ ( txid,+ case (viewTaprootWitness ctx witness, importXOnlyPubKey ctx (BS.drop 2 spentScript)) of+ (Just (ScriptPathSpend spd), Just outputKey) -> verifyScriptPathData ctx outputKey spd+ _ -> False+ )+ filter (not . snd) (map verdict spends) `shouldBe` []++ describe "The signature digest, on transactions that Bitcoin Core 32.1 mined on regtest" $ do+ let digestOf tx spent hashType annex leaf = fmap (encodeHex . runPutS . serialize) (taprootSigHash tx spent 0 hashType annex leaf)+ coin = TxOut 100000 . hexBytes+ digest32 = fromJust . digestFromByteString @SHA256 . hexBytes+ it "of a key path spend with an annex commits to the annex with its length" $+ digestOf (txOf keyPathAnnexTx) [coin "512028066f61c28af1ecb48c7948f8b5770838eec15223c8697a8e40510c2c29ea04"] sigHashDefault (Just (hexBytes "5068656c6c6f")) Nothing+ `shouldBe` Right "36a6017a9e1b1d246643172c6f0a9fe93136afd5f3e12d43b29a1f52a70a014f"+ it "of a script path spend with an annex and SIGHASH_ALL" $+ digestOf (txOf scriptPathAnnexTx) [coin "5120a354b6c111a5f8339fd8ea5bc6147f8ca829029188e4d3f7622751417328be18"] sigHashAll (Just (hexBytes "50616e6e6578206f662061207363726970742070617468")) (Just (digest32 "3da27bc75f49edd14e69e2cb4a3de1b00d614fd6049f2b5bb3403e39f4dbef51", 0xffffffff))+ `shouldBe` Right "1a8471791448654da927da98e00df97d853a79a5cc9b903ea59562dc2df4d591"+ it "of a script path spend commits to the position of the last OP_CODESEPARATOR executed" $ do+ let tx = txOf codeSeparatorTx+ spent = [coin "512057d7dc3026daad454ab05b7a533b5d3746fc726234ee966d0bc1464f19837426"]+ leaf = digest32 "9e8a23f6730007920039b3d164b85c54f69ff80f103fbf70c837e25918ffec59"+ -- The first key signs before the separator: position 0xffffffff+ digestOf tx spent sigHashDefault Nothing (Just (leaf, 0xffffffff)) `shouldBe` Right "0adc9a14a76ffe44333da52277f6cf8a469f9cd0caaceaf06283f204c06ed7e3"+ -- The second key signs after the separator, which is opcode number 2+ digestOf tx spent sigHashDefault Nothing (Just (leaf, 2)) `shouldBe` Right "057a66c7ba4129c6d30565056a76a6ac7189ebae58ec6ff7ef630ea7ad24bca9"+ it "is refused for an annex that does not start with 0x50, or that is empty" $ do+ let tx = txOf keyPathAnnexTx+ spent = [coin "512028066f61c28af1ecb48c7948f8b5770838eec15223c8697a8e40510c2c29ea04"]+ digestOf tx spent sigHashDefault (Just "hello") Nothing `shouldBe` Left "taprootSigHash: an annex starts with 0x50"+ digestOf tx spent sigHashDefault (Just "") Nothing `shouldBe` Left "taprootSigHash: an annex starts with 0x50"+ it "is refused with SIGHASH_SINGLE at the first input that has no output at its index" $+ -- The transaction of the BIP 341 vector has two outputs: input 2 is+ -- the first input without one+ taprootSigHash testKeyPath.unsignedTx testKeyPath.spent 2 sigHashSingle Nothing Nothing+ `shouldBe` Left "taprootSigHash: SIGHASH_SINGLE without an output at the input index"++ describe "verifyStdTx on Taproot inputs" $ do+ let coinOfTx = oneCoin ctx+ it "accepts the key path spend with an annex that Bitcoin Core 32.1 mined on regtest" $ do+ let tx = txOf keyPathAnnexTx+ verifyStdTx btc ctx tx (coinOfTx tx "512028066f61c28af1ecb48c7948f8b5770838eec15223c8697a8e40510c2c29ea04") `shouldBe` True+ it "accepts the script path spend of a single key leaf with an annex that Bitcoin Core 32.1 mined on regtest" $ do+ let tx = txOf scriptPathAnnexTx+ verifyStdTx btc ctx tx (coinOfTx tx "5120a354b6c111a5f8339fd8ea5bc6147f8ca829029188e4d3f7622751417328be18") `shouldBe` True+ -- The witness holds a valid signature of the leaf key over the leaf+ -- "<key> OP_CHECKSIG", and a control block that names the same key as+ -- internal key. The coin's output key does not commit to that leaf.+ it "refuses a script path spend whose leaf the output key of the coin does not commit to" $ do+ let tx = txOf uncommittedLeafTx+ verifyStdTx btc ctx tx (coinOfTx tx "5120da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21") `shouldBe` False+ it "refuses a Taproot input that has an input script" $ do+ let tx = txOf keyPathAnnexTx+ withScript = tx {inputs = [i {script = "\x51"} | i <- tx.inputs]} :: Tx+ verifyStdTx btc ctx withScript (coinOfTx tx "512028066f61c28af1ecb48c7948f8b5770838eec15223c8697a8e40510c2c29ea04") `shouldBe` False++ -- BIP 342 defines the rules of leaf version 0xc0 only. A leaf of another+ -- version is not a Tapscript, so its key check is not something this+ -- function can vouch for.+ it "verifies the script path spend of a single key leaf of version 0xc0, and not of version 0xc2" $ do+ let (leafSecret, leafPublic) = bip86Key ctx+ internal = fromJust (importXOnlyPubKey ctx (hexBytes "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))+ leafScript = Script [opPushData (exportXOnlyPubKey ctx leafPublic.point), OP_CHECKSIG]+ outpoint = OutPoint "1111111111111111111111111111111111111111111111111111111111111111" 0+ spendWith version = do+ let output = TaprootOutput internal (Just (MASTLeaf version leafScript))+ coinScript = taprootScriptOutput ctx output+ unsigned = buildTx ctx [outpoint] [(coinScript, 99000)]+ digest <- taprootSigHash unsigned [TxOut 100000 (marshal ctx coinScript)] 0 sigHashDefault Nothing (Just (leafHash version leafScript, 0xffffffff))+ signature <- taprootSignature (signSchnorr ctx leafSecret.key (hashToMsg digest)) sigHashDefault+ witness <-+ encodeTaprootWitness ctx . ScriptPathSpend $+ ScriptPathData {annex = Nothing, stack = [encodeTaprootSig signature], script = leafScript, extIsOdd = odd (keyParity ctx (taprootOutputKey ctx output)), leafVersion = version, internalKey = internal, control = [], opaqueScript = Nothing}+ return (verifyStdTx btc ctx (Tx unsigned.version unsigned.inputs unsigned.outputs [witness] unsigned.locktime) [(coinScript, 100000, outpoint)])+ spendWith 0xc0 `shouldBe` Right True+ spendWith 0xc2 `shouldBe` Right False++ describe "Control blocks" $+ it "are refused when the internal key is not the x coordinate of a point" $+ -- ff…ff is above the field size, so it is no x coordinate+ decodeControlBlock ctx ("\xc0" <> BS.replicate 32 0xff) `shouldBe` Left "decodeControlBlock: the internal key is not an x-only public key"++ describe "X-only keys as text and bytes" $ do+ let x = "7a338b2a113a14b5a008560cbf5d3c46a6c282967c05f739f30668d32fc538b5"+ json t = unmarshalJSON ctx (encode (String t)) :: Maybe XOnlyPubKey+ it "are read from the hex of 32 bytes" $+ fmap (encodeHex . marshal ctx) (json x) `shouldBe` Just x+ it "are refused from the hex of 33 bytes, of 64 bytes and of 31 bytes" $ do+ json (x <> "ff") `shouldBe` Nothing+ json (x <> x) `shouldBe` Nothing+ json (Data.Text.dropEnd 2 x) `shouldBe` Nothing+ it "are refused from 33 bytes" $+ (unmarshal ctx (hexBytes x <> "\xff") :: Either String XOnlyPubKey) `shouldSatisfy` isLeft++ describe "The depth limit of a script tree" $ do+ -- BIP 341: a control block holds at most 128 nodes, so a leaf deeper+ -- than 128 can never be spent. Bitcoin Core's TaprootBuilder refuses a+ -- depth above TAPROOT_CONTROL_MAX_NODE_COUNT = 128.+ let leaf = MASTLeaf 0xc0 (Script [OP_1])+ -- A tree whose two deepest leaves are at depth n+ chain :: Int -> MAST+ chain 0 = leaf+ chain n = MASTBranch leaf (chain (n - 1))+ chainLeaves :: Int -> [TapLeaf]+ chainLeaves n = [TapLeaf (fromIntegral d) 0xc0 (Script [OP_1]) | d <- [1 .. n]] <> [TapLeaf (fromIntegral n) 0xc0 (Script [OP_1])]+ it "lists the leaves of a tree whose deepest leaves are at depth 128" $+ fmap (map (.depth)) (tapTreeLeaves (chain 128)) `shouldBe` Just ([1 .. 128] <> [128])+ it "does not list the leaves of a tree whose deepest leaves are at depth 129" $+ fmap length (tapTreeLeaves (chain 129)) `shouldBe` Nothing+ it "does not list the leaves of a tree whose deepest leaves are at depth 256" $+ fmap length (tapTreeLeaves (chain 256)) `shouldBe` Nothing+ it "builds the tree of leaves down to depth 128, and not down to depth 129" $ do+ fmap mastCommitment (tapTreeFromLeaves (chainLeaves 128)) `shouldBe` Just (mastCommitment (chain 128))+ fmap mastCommitment (tapTreeFromLeaves (chainLeaves 129)) `shouldBe` Nothing+ it "checkTapTree accepts a tree down to depth 128 and refuses one down to depth 129" $ do+ checkTapTree (chain 128) `shouldBe` Right ()+ checkTapTree (chain 129) `shouldBe` Left "checkTapTree: a node is deeper than 128"+ checkTapTree (MASTBranch leaf (MASTCommitment (mastCommitment leaf))) `shouldBe` Right ()+ it "checkTapTree refuses a leaf version that a control block cannot carry" $ do+ -- BIP 341: the leaf version is c[0] & 0xfe, so it is even, and it+ -- cannot be 0x50, which would read as an annex+ checkTapTree (MASTLeaf 0xc2 (Script [OP_1])) `shouldBe` Right ()+ checkTapTree (MASTLeaf 0xc1 (Script [OP_1])) `shouldBe` Left "checkTapTree: the leaf version 193 is odd"+ checkTapTree (MASTLeaf 0x50 (Script [OP_1])) `shouldBe` Left "checkTapTree: the leaf version 0x50 would read as an annex"+ it "the proof of a leaf at depth 128 verifies, and the proof of a leaf at depth 129 does not" $ do+ let (_, p) = bip86Key ctx+ internal = fromJust (importXOnlyPubKey ctx (exportXOnlyPubKey ctx p.point))+ deepest tree =+ let outputKey = taprootOutputKey ctx (TaprootOutput internal (Just tree))+ (version, script, proof) = last (getMerkleProofs tree)+ spend = ScriptPathData {annex = Nothing, stack = [], script, extIsOdd = odd (keyParity ctx outputKey), leafVersion = version, internalKey = internal, control = proof, opaqueScript = Nothing}+ in (length proof, verifyScriptPathData ctx outputKey spend)+ deepest (chain 128) `shouldBe` (128, True)+ deepest (chain 129) `shouldBe` (129, False)+ -- | 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)@@ -273,6 +594,112 @@ ("m/86'/0'/0'/1/0", "399f1b2f4393f29a18c937859c5dd8a77350103157eb880f02e8c08214277cef", "882d74e5d0572d5a816cef0041a96b6c1de832f6f9676d9605c44d5e9a97d3dc", "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7") ] +-- | The leaf hash as versions up to 1.5.1 computed it: the script length is+-- the base-128 varint of the bytes package, where BIP 341 has a CompactSize.+-- The two are the same byte for a length below 128 only.+leafHashBefore152 :: Word8 -> Script -> Digest SHA256+leafHashBefore152 version script =+ hashFinalize . hashUpdate (initTaggedHash "TapLeaf") . runPutS $ do+ putWord8 version+ serialize (Base128.VarInt (BS.length bytes))+ putByteString bytes+ where+ bytes = runPutS (serialize script)++-- | @taproot_tree_helper@ of BIP 341, written from the text of the BIP with+-- SHA256 alone: for each leaf its script bytes and its path, and the hash+-- of the tree. A branch known only by its commitment has no leaves.+referenceTree :: MAST -> ([(ByteString, [ByteString])], ByteString)+referenceTree = \case+ MASTLeaf version script ->+ let bytes = runPutS (serialize script)+ in ([(bytes, [])], tagged "TapLeaf" (BS.singleton version <> compactSize (BS.length bytes) <> bytes))+ MASTBranch l r ->+ let (left, leftHash) = referenceTree l+ (right, rightHash) = referenceTree r+ in ( [(leaf, path <> [rightHash]) | (leaf, path) <- left] <> [(leaf, path <> [leftHash]) | (leaf, path) <- right],+ tagged "TapBranch" (min leftHash rightHash <> max leftHash rightHash)+ )+ MASTCommitment commitment -> ([], BA.convert commitment)+ where+ hash32 = runPutS . serialize . sha256+ tagged tag message = hash32 (hash32 tag <> hash32 tag <> message)+ compactSize :: Int -> ByteString+ compactSize n+ | n < 0xfd = BS.singleton (fromIntegral n)+ | n <= 0xffff = BS.pack [0xfd, fromIntegral (n `mod` 256), fromIntegral (n `div` 256)]+ | otherwise = error "referenceTree: script too long for this test"++-- | The depth of each leaf of a tree, depth first+leafDepths :: MAST -> [Int]+leafDepths = go 0+ where+ go d = \case+ MASTBranch l r -> go (d + 1) l <> go (d + 1) r+ MASTLeaf {} -> [d]+ MASTCommitment {} -> []++-- | A script tree of depth 0 to 6. The scripts are 1 to 600 bytes long, so+-- that their lengths fall on both sides of 128 and of 253, where the+-- CompactSize of the leaf hash changes. With the flag set, one node in ten+-- is known only by its commitment.+arbitraryMAST :: Bool -> Gen MAST+arbitraryMAST withCommitments = choose (0, 6 :: Int) >>= go+ where+ go 0 = node+ go d = frequency [(1, node), (3, MASTBranch <$> go (d - 1) <*> go (d - 1))]+ node+ | withCommitments = frequency [(9, leaf), (1, commitment)]+ | otherwise = leaf+ leaf = MASTLeaf <$> elements [0xc0, 0xc0, 0xc0, 0xc2] <*> script+ commitment = MASTCommitment . fromJust . digestFromByteString . BS.pack <$> vectorOf 32 arbitrary+ script = do+ n <- frequency [(3, choose (1, 127)), (3, choose (128, 252)), (2, choose (253, 600)), (2, elements [127, 128, 252, 253])]+ return (Script (replicate n OP_NOP))++digestHex :: Digest SHA256 -> Text+digestHex = encodeHex . BA.convert++scriptOf :: Text -> Script+scriptOf = either error id . runGetS deserialize . hexBytes++-- | The coin of 100,000 satoshi that the first input of a transaction+-- spends, with the output script given in hex+oneCoin :: Ctx -> Tx -> Text -> [(ScriptOutput, Word64, OutPoint)]+oneCoin ctx tx script = [(either error id (unmarshal ctx (hexBytes script)), 100000, (head tx.inputs).outpoint)]++txOf :: Text -> Tx+txOf = either error id . S.decode . hexBytes++-- | multi_a(2,A,B,C,D) of BIP 387: <A> OP_CHECKSIG <B> OP_CHECKSIGADD <C>+-- OP_CHECKSIGADD <D> OP_CHECKSIGADD OP_2 OP_NUMEQUAL+multiALeaf :: Text+multiALeaf = "20b2fff6fd9636bea638845402774fa41b7ab1a68e757c669949fa2f35d0949fedac2053f8149cd733fed0b0eb50388db3f142638bdc819f6ae6ac5d81d0a8caebc9afba20a07f1b7d9804bdc3cf63f30b8f69b2ef53b0fc68d9253ed61b9c31593f5cb075ba20b7e3bfb9e6cb88e668fee38674cef6d155bba19a5cc7403678bf91a7b7a52725ba529c"++-- | Transactions that Bitcoin Core 32.1 accepted in a regtest block. Each+-- spends one coin of 100,000 satoshi.+keyPathAnnexTx, scriptPathAnnexTx, codeSeparatorTx :: Text+keyPathAnnexTx = "010000000001016a7d9c7f155cc46f63ab350387f74b6a585d7a179941592d6fb2151e384740810100000000ffffffff01d07e010000000000160014cabe51bbc4203c9221901541c9e9d3ac219e6ca80240dac56ae755419956efbb11ff69d46916c956612e03bdafe382e48ca1fc4bdf12fc768daf297548a89d22f19451c08107e763523a88d10b9a5044de20789895d2065068656c6c6f00000000"+scriptPathAnnexTx = "010000000001016be84449b8f2ec0559b321b2aed59ba273f79710a73c5a7e3cbce8e57178c2260100000000ffffffff01d07e010000000000160014cabe51bbc4203c9221901541c9e9d3ac219e6ca80441fe2a5388123bdc28dc0635796683a24418375c1e94b9d9074889d4b6865224dd6a0ae16d63d454d1dcdbd86f7291f5be5ce2d0cb785adddc1cbd9bd9b8cda3bd012220864f1c660fd3842d2b45acae2d2c181378c3c397d9c8f6a36dce7e1b3f2fa01fac21c1bc4d541d1be26036455459e9fd4fb8c9e2b829b17eb18fb5f66bbbdedc1c61681750616e6e6578206f66206120736372697074207061746800000000"+codeSeparatorTx = "01000000000101a1b61db207932647112026e232f793c6d807f666f46ebb23b526e68342630f820100000000ffffffff01d07e010000000000160014cabe51bbc4203c9221901541c9e9d3ac219e6ca8044093e3dfc003f3d11aca5c6301aa52f55fe916b01036710653c56ff829d393489a26834188c932372f93aadf116a55c540f68e049f2b17b69760ea22190b944b57401f03095eb9fe08f9462b86196fccd3dfffc2702c6c0b186593bed2ae31f4b963a5b36a40a4c0c76c3da7caf03f39b358773ce914999a772d638d3f5f18d49aa945201da5064215e4cad73afbc2f5cc5d1c537452b49b3660ab201ef739b808950e5cadab203ef9208927082a726fea24d87dabbb476a30c669f1aa0d98bdfc29ad7e30d33dac21c0db5c3a57b4b60a18c53d60623fc64fbd97c21fe61e9a0508919f1bbe7e658aa900000000"++-- | A script path spend of a coin whose output key does not commit to the+-- leaf. The leaf is "<key> OP_CHECKSIG", its signature is valid, and the+-- control block names the same key as internal key.+uncommittedLeafTx :: Text+uncommittedLeafTx = "0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff01b8820100000000001600149b652a14674a506079f574d20ca7daef6f9a66bb0340ef928ef7a11908475a2f841e482cf8e72e356cccd6f2a1d0c954527fcc2f6f9a49e62e422d55bbacbabb1676fdcc422ff5fc8ad6b0dfcb29219af450769da1602220c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac21c0c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee500000000"++-- | The spends of data/taproot_mainnet_spends.json: transaction id, output+-- script of the coin spent, witness items+mainnetSpendsParseJSON :: Value -> Parser [(Text, ByteString, [ByteString])]+mainnetSpendsParseJSON = withObject "mainnet spends" $ \obj -> do+ spends <- obj .: "spends"+ forM spends $ \s ->+ (,,)+ <$> s .: "txid"+ <*> (s .: "spentScript" >>= jsonHex)+ <*> (s .: "witness" >>= mapM jsonHex)+ hexBytes :: Text -> ByteString hexBytes = fromJust . decodeHex @@ -322,7 +749,7 @@ -- The control block is the last item of the witness: the stack is -- empty, so it comes after the script calculatedControlBlocks =- last . encodeTaprootWitness ctx . ScriptPathSpend <$> scriptPathSpends+ either error last . encodeTaprootWitness ctx . ScriptPathSpend <$> scriptPathSpends scriptPathSpends = mkScriptPathSpend <$> maybe mempty getMerkleProofs theOutput.mast mkScriptPathSpend (leafVersion, script, proof) =@@ -333,9 +760,10 @@ extIsOdd = odd $ keyParity ctx theOutputKey, leafVersion, internalKey = theOutput.internalKey,- control = proof+ control = proof,+ opaqueScript = Nothing }- onExamples = zipWithM_ (@?=) calculatedControlBlocks+ onExamples expected = map encodeHex calculatedControlBlocks @?= map encodeHex expected checkVerification = assertBool "Script verifies" . verifyScriptPathData ctx theOutputKey testAddress :: Ctx -> TestScriptPubKey -> IO ()@@ -343,7 +771,7 @@ computedAddress @?= (Just . spkeAddress . tspkExpected) testData where computedAddress =- (addrToText btc <=< outputAddress ctx)+ (addrToText btc <=< outputAddress btc ctx) . taprootScriptOutput ctx $ tspkGiven testData @@ -461,7 +889,7 @@ <*> i .: "sigHash" <*> e .: "witness" aux <- obj .: "auxiliary"- signedRaw <- aux .: "fulledSignedTx" >>= jsonHex+ signedRaw <- aux .: "fullySignedTx" >>= jsonHex signed <- either fail return (S.decode signedRaw) return (KeyPathVector tx spent cases signed)
test/Haskoin/TransactionSpec.hs view
@@ -12,7 +12,7 @@ import Control.Exception (evaluate) import Control.Monad (forM_, guard, unless)-import Data.Aeson (decode, eitherDecode)+import Data.Aeson (decode, eitherDecode, withObject, (.:)) import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.Bytes.Get@@ -20,13 +20,13 @@ import Data.Bytes.Serial import Data.Default (def) import Data.Either-import Data.List (find, inits, isInfixOf)+import Data.List (find, inits, isInfixOf, isPrefixOf) 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 Data.Word (Word32, Word64, Word8) import Haskoin.Address import Haskoin.Crypto import Haskoin.Network.Constants@@ -115,7 +115,12 @@ bytesSpec ctx sizeSpec ctx coinSelectionSpec+ buildSpec ctx verificationSpec ctx+ coreSignedSpec ctx+ consensusSpec ctx+ thiefSpec ctx+ signerSpec ctx mergeSpec ctx taprootSigningSpec ctx segwitSpec ctx@@ -333,17 +338,24 @@ _ -> 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.+-- change, and weighs no more than nodes relay. The sums are made as+-- unbounded integers, so that a sum that wraps around cannot pass. When+-- there is no selection, no prefix of the coins both fits that weight and+-- 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+ (value chosen, weight chosen <= maxStandardTxWeight)+ === (toInteger target + toInteger change + fee chosen, True) Left _ ->- property $ target == 0 || all (\prefix -> sum (map coinValue prefix) < target + fee prefix) (drop 1 (inits coins))+ property $ target == 0 || not (any covers (takeWhile ((<= maxStandardTxWeight) . weight) (drop 1 (inits coins)))) where- fee cs = guessTxFee byteFee (map coinInputKind cs) outs+ value = sum . map (toInteger . coinValue)+ weight cs = guessTxWeight (map coinInputKind cs) outs+ -- The fee rate times the virtual size, which is the weight over four, rounded up+ fee cs = toInteger byteFee * toInteger ((weight cs + 3) `div` 4)+ covers prefix = value prefix >= toInteger target + fee prefix {- Signing Transactions -} @@ -374,7 +386,7 @@ signNestedWitnessTx net ctx txSigP sigis [(head prv).key] verData = handleSegwit <$> sigis handleSegwit (SigInput s v o _ _)- | isSegwit s = (toP2SH (either error id (encodeOutput ctx s)), v, o)+ | isSegwit s = (either error id (toP2SH =<< encodeOutput ctx s), v, o) | otherwise = (s, v, o) testMergeTx :: Network -> Ctx -> ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)]) -> Bool@@ -420,6 +432,40 @@ parseTx :: Text -> Either String Tx parseTx = runGetS deserialize . hexBytes +-- | The key pair whose secret key is the number given, in compressed form.+-- Tests use these fixed keys, so that a failure can be run again.+fixedKey :: Ctx -> Word8 -> (PrivateKey, PublicKey)+fixedKey ctx n = (k, derivePublicKey ctx k)+ where+ k = wrapSecKey True (fromJust (secKey (B.replicate 31 0 <> B.singleton n)))++-- | The same key pair with the public key in uncompressed form+fixedKeyU :: Ctx -> Word8 -> (PrivateKey, PublicKey)+fixedKeyU ctx n = (k, derivePublicKey ctx k)+ where+ k = wrapSecKey False (fst (fixedKey ctx n)).key++-- | HASH160 of a public key as it is serialized+keyHash :: Ctx -> PublicKey -> Hash160+keyHash ctx = addressHash . marshal ctx++-- | A coin to spend: an output of the genesis coinbase transaction id+coinAt :: Word32 -> OutPoint+coinAt = OutPoint (fromJust (hexToTxHash genesisTxId))++-- | The transaction with the script of one input replaced+withInputScript :: Tx -> Int -> ByteString -> Tx+withInputScript tx i script =+ Tx tx.version (zipWith replace [0 :: Int ..] tx.inputs) tx.outputs tx.witness tx.locktime+ where+ replace j input+ | j == i = TxIn input.outpoint script input.sequence+ | otherwise = input++-- | The transaction with its witness replaced+withWitness :: Tx -> WitnessData -> Tx+withWitness tx stacks = Tx tx.version tx.inputs tx.outputs stacks tx.locktime+ idsSpec :: Spec idsSpec = describe "Transaction ids" $ do@@ -490,6 +536,14 @@ 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 "decodeTx reads a transaction on its own, and refuses bytes after it" $ do+ let legacy = "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000"+ fmap txHash (decodeTx (hexBytes legacy)) `shouldBe` fmap txHash (parseTx legacy)+ fmap (length . (.inputs)) (decodeTx (hexBytes legacy)) `shouldBe` Right 1+ decodeTx (hexBytes (legacy <> "00")) `shouldSatisfy` either ("Trailing bytes after transaction" `isInfixOf`) (const False)+ decodeTx (hexBytes bip143SignedTx <> "\x00") `shouldSatisfy` either ("Trailing bytes after transaction" `isInfixOf`) (const False)+ fmap txWeight (decodeTx (hexBytes bip143SignedTx)) `shouldBe` Right 1042+ it "refuse a literal with bytes after the transaction" $ do let legacy = "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000" length (fromString legacy :: Tx).inputs `shouldBe` 1@@ -508,9 +562,46 @@ 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"]]+ -- A second stack has no input to go with, so its bytes are not written+ fmap (.witness) (decodeTx (runPutS (serialize tx {witness = [["ab"], ["cd"]]}))) `shouldBe` Right [["ab"]] + -- Only an input has a witness. A stack beyond the inputs is not part of+ -- the transaction, so it must not turn the witness format on.+ it "a witness item in a stack beyond the inputs is not a witness" $ do+ let legacy = "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000"+ tx <- either fail return (parseTx legacy)+ let beyond = withWitness tx [[], ["ab"]]+ hasWitness beyond `shouldBe` False+ encodeHex (runPutS (serialize beyond)) `shouldBe` legacy+ txWitnessHash beyond `shouldBe` txHash tx+ -- 85 bytes, four weight units each+ txWeight beyond `shouldBe` 340++ it "write an empty stack for an input that the witness list leaves out" $ do+ let tx = Tx 1 [TxIn (coinAt 0) "" 0xffffffff, TxIn (coinAt 1) "" 0xffffffff] [TxOut 1 "\x51"] [["ab"]] 0+ input n = "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" <> n <> "00" <> "ffffffff"+ -- version, marker and flag, two inputs, one output of 1 satoshi to+ -- OP_1, the stack of one item "ab", the empty stack, lock time+ encodeHex (runPutS (serialize tx))+ `shouldBe` "01000000" <> "0001" <> "02" <> input "00000000" <> input "01000000" <> "01" <> "0100000000000000" <> "0151" <> "0102" <> "6162" <> "00" <> "00000000"++ it "the JSON form of a transaction has one witness stack per input, or none" $ do+ let json stacks = cs ("{\"version\":1,\"inputs\":[{\"prevoutput\":{\"txid\":\"" <> genesisTxId <> "\",\"index\":0},\"inputscript\":\"\",\"sequence\":4294967295}],\"outputs\":[],\"witnessdata\":" <> stacks <> ",\"locktime\":0}")+ fmap (.witness) (decode (json "[]") :: Maybe Tx) `shouldBe` Just []+ fmap (.witness) (decode (json "[[\"6162\"]]") :: Maybe Tx) `shouldBe` Just [["ab"]]+ (decode (json "[[\"6162\"],[\"6364\"]]") :: Maybe Tx) `shouldBe` Nothing+ (decode "{\"version\":1,\"inputs\":[],\"outputs\":[],\"witnessdata\":[[\"6162\"]],\"locktime\":0}" :: Maybe Tx) `shouldBe` Nothing++ -- 253 is the first count that takes three bytes: fd, then 253 as two+ -- bytes. Each input without a script is 41 bytes, and the output of an+ -- empty script 9: 4 + 3 + 253 × 41 + 1 + 9 + 4 = 10394 bytes.+ it "write and read a count of 253 inputs in three bytes" $ do+ let tx = Tx 1 [TxIn (coinAt i) "" 0xffffffff | i <- [0 .. 252]] [TxOut 1 ""] [] 0+ bytes = runPutS (serialize tx)+ B.length bytes `shouldBe` 10394+ encodeHex (B.take 3 (B.drop 4 bytes)) `shouldBe` "fdfd00"+ fmap (length . (.inputs)) (runGetS deserialize bytes :: Either String Tx) `shouldBe` Right 253+ 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@@ -553,18 +644,17 @@ 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) + -- A redeem script is at most 520 bytes, so 15 compressed keys are the+ -- most of a pay-to-script-hash multisig+ it "counts the largest pay-to-script-hash multisig, 15 of 15" $+ -- OP_15, 15 keys of 34 (510), OP_15, OP_CHECKMULTISIG: a 513-byte script.+ -- OP_0, 15 signatures of 73 (1095), the script pushed with OP_PUSHDATA2 (516): 1612 bytes after a 3-byte length, and 40+ inputWeight (InputMulSig 15 15) `shouldBe` (1655, 0)+ it "counts the bytes of each kind of output" $ do outputSize OutputPKHash `shouldBe` 34 outputSize OutputScriptHash `shouldBe` 32@@ -611,15 +701,45 @@ 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+ guessTxFee 10 [InputPKHash] [OutputPKHash] `shouldBe` Right 1920+ guessTxFee 10 [InputWitnessPKHash] [OutputWitnessPKHash] `shouldBe` Right 1100+ guessTxFee 0 [InputPKHash] [OutputPKHash] `shouldBe` Right 0 + -- 192 bytes at 2^58 satoshi per byte is three times 2^64+ it "refuses a fee that does not fit in 64 bits" $ do+ guessTxFee 288230376151711744 [InputPKHash] [OutputPKHash] `shouldBe` Left "guessTxFee: The fee does not fit in 64 bits"+ -- 192 × 96076792050570581 = 2^64 - 64, the largest multiple of 192 below 2^64+ guessTxFee 96076792050570581 [InputPKHash] [OutputPKHash] `shouldBe` Right 18446744073709551552+ guessTxFee 96076792050570582 [InputPKHash] [OutputPKHash] `shouldBe` Left "guessTxFee: The fee does not fit in 64 bits"++ it "refuses a kind of input or output that cannot exist" $ do+ -- 16 compressed keys make a redeem script of 547 bytes+ guessTxFee 1 [InputMulSig 16 16] [OutputPKHash] `shouldBe` Left "guessTxFee: The input kind InputMulSig 16 16 is not valid: m of n needs 1 <= m <= n <= 15"+ guessTxFee 1 [InputMulSig 3 2] [OutputPKHash] `shouldBe` Left "guessTxFee: The input kind InputMulSig 3 2 is not valid: m of n needs 1 <= m <= n <= 15"+ guessTxFee 1 [InputWitnessMulSig 0 2] [OutputPKHash] `shouldBe` Left "guessTxFee: The input kind InputWitnessMulSig 0 2 is not valid: m of n needs 1 <= m <= n <= 20"+ guessTxFee 1 [InputNestedWitnessMulSig 1 21] [OutputPKHash] `shouldBe` Left "guessTxFee: The input kind InputNestedWitnessMulSig 1 21 is not valid: m of n needs 1 <= m <= n <= 20"+ guessTxFee 1 [InputPKHash] [OutputScript (-1)] `shouldBe` Left "guessTxFee: The output kind OutputScript (-1) is not valid: a script has from 0 to 10000 bytes"+ chooseCoins 1000 1 [OutputPKHash] False [TestCoin 100000 (InputMulSig (-30) 2)]+ `shouldBe` Left "chooseCoins: The input kind InputMulSig (-30) 2 is not valid: m of n needs 1 <= m <= n <= 15"++ -- GetDustThreshold of Bitcoin Core at 3 satoshi per byte: the output+ -- and the smallest input that spends it, 148 bytes without a witness+ -- and 67 with one+ it "gives the dust threshold of each kind of output" $ do+ -- (34 + 148) × 3+ dustThreshold OutputPKHash `shouldBe` 546+ -- (32 + 148) × 3+ dustThreshold OutputScriptHash `shouldBe` 540+ -- (31 + 67) × 3+ dustThreshold OutputWitnessPKHash `shouldBe` 294+ -- (43 + 67) × 3+ dustThreshold OutputWitnessScriptHash `shouldBe` 330+ dustThreshold OutputTaproot `shouldBe` 330+ -- a bare 1-of-1 multisig: (46 + 148) × 3+ dustThreshold (OutputScript 37) `shouldBe` 582+ 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+ let (k, p) = fixedKey ctx 1 h = fromJust (addressHash160 (pubKeyAddr ctx p)) op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0 witnessTx = buildTx ctx [op] [(PayWitnessPKHash h, 90000)]@@ -627,9 +747,14 @@ 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)+ -- 82 bytes without witness. The witness: marker and flag 2, count 1,+ -- the signature with its hash type after its length, 72 or 73, and+ -- the key after its length 34: 437 or 438 units, 110 virtual bytes+ -- either way+ txVirtualSize signedWitness `shouldBe` 110+ -- 94 bytes without witness. The witness: 2, count 1, a 64-byte+ -- signature after its length 65: 444 units, one less than the guess+ txVirtualSize signedTaproot `shouldBe` 111 {- Coin selection -} @@ -659,11 +784,59 @@ 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) + -- One P2PKH input and one P2PKH output are 192 bytes: 1920 satoshi+ it "selects a coin that covers the target and the fee exactly, with no change" $+ chooseCoins 98080 10 [OutputPKHash] False [TestCoin 100000 InputPKHash] `shouldBe` Right ([TestCoin 100000 InputPKHash], 0)++ -- With no fee, each coin alone covers the target, and the second is no+ -- smaller than the first+ it "keeps the first solution when a later one is not smaller" $+ chooseCoins 60000 0 [OutputPKHash] True [TestCoin 70000 InputPKHash, TestCoin 70000 InputWitnessPKHash]+ `shouldBe` Right ([TestCoin 70000 InputPKHash], 10000)++ -- 2^64 - 101 plus the fee of 192 is above 2^64, and must not wrap+ -- around to 91+ it "refuses a target that the coins cannot cover, even when target and fee add up to more than 2^64" $ do+ chooseCoins 18446744073709551515 1 [OutputPKHash] False [TestCoin 1000 InputPKHash] `shouldBe` Left "chooseCoins: No solution found"+ -- 192 bytes at 2^58 satoshi per byte is three times 2^64+ chooseCoins 60000 288230376151711744 [OutputPKHash] False [TestCoin 100000 InputPKHash] `shouldBe` Left "chooseCoins: No solution found"++ -- 1 + (2^64 - 1) = 2^64 covers 5 with 2^64 - 5 of change+ it "adds coin values without wrapping around" $+ chooseCoins 5 0 [OutputPKHash] False [TestCoin 1 InputPKHash, TestCoin 18446744073709551615 InputPKHash]+ `shouldBe` Right ([TestCoin 18446744073709551615 InputPKHash, TestCoin 1 InputPKHash], 18446744073709551611)++ -- k P2WPKH inputs and one P2WPKH output, for k from 253: 4 × (8 + 3 ++ -- 41 k + 1 + 31) + 2 + 108 k = 174 + 272 k weight units. 1469 inputs+ -- are 399742 units, 99936 virtual bytes, and 1470 are 400014, above the+ -- 400000 that nodes relay.+ it "selects up to the standard transaction weight and no further" $ do+ let coins = replicate 2000 (TestCoin 10000 InputWitnessPKHash)+ -- 1469 coins hold 14690000, less 99936 of fee at 1 satoshi per byte+ fmap (\(chosen, change) -> (length chosen, change)) (chooseCoins 14590064 1 [OutputWitnessPKHash] False coins) `shouldBe` Right (1469, 0)+ chooseCoins 14590065 1 [OutputWitnessPKHash] False coins `shouldBe` Left "chooseCoins: The selection exceeds the standard transaction weight of 400000"+ 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" +{- Building -}++buildSpec :: Ctx -> Spec+buildSpec ctx =+ describe "Building a transaction to addresses" $ do+ let address = "14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb"+ -- AmountFromValue of Bitcoin Core refuses an amount out of range when+ -- createrawtransaction builds a transaction+ it "refuses an amount above the 21 million coins that can exist" $ do+ buildAddrTx btc ctx [coinAt 0] [(address, 2100000000000000)] `shouldSatisfy` isRight+ buildAddrTx btc ctx [coinAt 0] [(address, 2100000000000001)] `shouldBe` Left "buildAddrTx: An amount is above the maximum of 2100000000000000 satoshi"+ buildAddrTx btc ctx [coinAt 0] [(address, 18446744073709551615)] `shouldBe` Left "buildAddrTx: An amount is above the maximum of 2100000000000000 satoshi"++ it "refuses an address of another network" $+ buildAddrTx bch ctx [coinAt 0] [("bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", 1000)] `shouldBe` Left "buildAddrTx: Invalid address bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"+ {- Verification -} verificationSpec :: Ctx -> Spec@@ -671,8 +844,8 @@ 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 (k, p) = fixedKey ctx 1+ (_, other) = fixedKey ctx 2 let h = fromJust (addressHash160 (pubKeyAddr ctx p)) otherHash = fromJust (addressHash160 (pubKeyAddr ctx other)) op = coinOf 0@@ -690,7 +863,7 @@ 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 (k, p) = fixedKey ctx 1 let h = fromJust (addressHash160 (pubKeyAddr ctx p)) ops = [coinOf 0, coinOf 1] tx = buildTx ctx ops [(PayPKHash h, 150000)]@@ -704,7 +877,7 @@ 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 (k, p) = fixedKey ctx 1 let h = fromJust (addressHash160 (pubKeyAddr ctx p)) ops = [coinOf 0, coinOf 1] -- one output, so input 1 has none at its index@@ -722,7 +895,7 @@ -- 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 (k, p) = fixedKey ctx 1 let h = fromJust (addressHash160 (pubKeyAddr ctx p)) ops = [coinOf 0, coinOf 1] tx = buildTx ctx ops [(PayPKHash h, 150000)]@@ -735,7 +908,7 @@ 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 (k, p) = fixedKey ctx 1 let h = fromJust (addressHash160 (pubKeyAddr ctx p)) op = coinOf 0 tx = buildTx ctx [op] [(PayWitnessPKHash h, 90000)]@@ -752,10 +925,10 @@ 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 (k1, p1) = fixedKey ctx 1+ (k2, p2) = fixedKey ctx 2 let ms = PayMulSig [p1, p2] 2- so = PayScriptHash (fromJust (addressHash160 (payToScriptAddress ctx ms)))+ so = PayScriptHash (fromJust (either (const Nothing) 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]@@ -773,10 +946,47 @@ complete <- either fail return (mergeTxs btc ctx [a, b, c, d] coins) verifyStdTx btc ctx complete coins `shouldBe` True + it "keeps the witness of an input that is not merged" $ do+ let (k1, p1) = fixedKey ctx 1+ (k2, p2) = fixedKey ctx 2+ (k3, p3) = fixedKey ctx 3+ ms = PayMulSig [p1, p2] 2+ so = either error id (toP2SH =<< encodeOutput ctx ms)+ wpkh = PayWitnessPKHash (keyHash ctx p3)+ tx = buildTx ctx [coinAt 0, coinAt 1] [(PayPKHash (keyHash ctx p1), 150000)]+ msSigi = SigInput so 100000 (coinAt 0) sigHashAll (Just ms)+ coins = [(so, 100000, coinAt 0), (wpkh, 100000, coinAt 1)]+ withSegwit <- either fail return (signTx btc ctx tx [SigInput wpkh 100000 (coinAt 1) sigHashAll Nothing] [k3.key])+ a <- either fail return (signTx btc ctx withSegwit [msSigi] [k1.key])+ b <- either fail return (signTx btc ctx withSegwit [msSigi] [k2.key])+ merged <- either fail return (mergeTxs btc ctx [a, b] [(so, 100000, coinAt 0)])+ merged.witness `shouldBe` withSegwit.witness+ verifyStdTx btc ctx merged coins `shouldBe` True++ it "mergeTxInput refuses an index that a transaction does not have" $ do+ let (_, p1) = fixedKey ctx 1+ ms = PayMulSig [p1] 1+ tx = buildTx ctx [coinAt 0] []+ short = buildTx ctx [] []+ mergeTxInput btc ctx [tx] tx ((ms, 100000), 3) `shouldBe` Left "mergeTxInput: input index out of range"+ mergeTxInput btc ctx [tx, short] tx ((ms, 100000), 0) `shouldBe` Left "mergeTxInput: input index out of range"++ it "refuses to merge inputs that carry different redeem scripts" $ do+ let (k1, p1) = fixedKey ctx 1+ (k2, p2) = fixedKey ctx 2+ (_, p3) = fixedKey ctx 3+ msA = PayMulSig [p1, p2] 2+ msB = PayMulSig [p1, p2, p3] 2+ hashOf ms = either error id (toP2SH =<< encodeOutput ctx ms)+ tx = buildTx ctx [coinAt 0] [(PayPKHash (keyHash ctx p1), 90000)]+ a <- either fail return (signTx btc ctx tx [SigInput (hashOf msA) 100000 (coinAt 0) sigHashAll (Just msA)] [k1.key])+ b <- either fail return (signTx btc ctx tx [SigInput (hashOf msB) 100000 (coinAt 0) sigHashAll (Just msB)] [k2.key])+ mergeTxs btc ctx [a, b] [(hashOf msA, 100000, coinAt 0)] `shouldBe` Left "Redeem scripts do not match"+ 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 (k1, p1) = fixedKey ctx 1+ (k2, p2) = fixedKey ctx 2+ (k3, p3) = fixedKey ctx 3 let ms = PayMulSig [p1, p2, p3] 2 msScript = either error id (encodeOutput ctx ms) op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0@@ -785,6 +995,7 @@ 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+ -- The count is not a verdict: verifyStdInput refuses this input 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@@ -799,7 +1010,7 @@ 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 (k, p) = fixedKey ctx 1 let op = OutPoint (fromJust (hexToTxHash genesisTxId)) 0 tx = buildTx ctx [op] [] sig = TxSignature (signHash ctx k.key (sha256 ("digest" :: ByteString))) sigHashAll@@ -824,7 +1035,7 @@ 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 (k, p) = fixedKey ctx 1 let so = taprootScriptOutput ctx (TaprootOutput p.point Nothing) tx = buildTx ctx [op] [(so, 90000)] sigi = SigInput so 100000 op sigHashDefault Nothing@@ -840,11 +1051,11 @@ 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)+ let (other, _) = fixedKey ctx 2 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 (k, p) = fixedKey ctx 1 let so = taprootScriptOutput ctx (TaprootOutput p.point Nothing) other = OutPoint (fromJust (hexToTxHash genesisTxId)) 1 tx = buildTx ctx [op, other] [(so, 90000)]@@ -859,15 +1070,15 @@ segwitSpec :: Ctx -> Spec segwitSpec ctx = describe "SegWit witness helpers" $ do- (k, p) <- runIO (generate (arbitraryKeyPair ctx))- let digest = sha256 ("message" :: ByteString)+ let (k, p) = fixedKey ctx 1+ 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+ wsh = either error id (toP2WSH multisigScript) it "isSegwit is true for the version 0 programs only" $ do isSegwit (PayWitnessPKHash h) `shouldBe` True isSegwit wsh `shouldBe` True@@ -899,7 +1110,7 @@ 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 (either error id (toP2SH =<< 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" @@ -916,3 +1127,329 @@ txHashToHex (txHash mainnetGenesisTx) `shouldBe` "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" it "the testnet4 coinbase has its own id" $ txHashToHex (txHash testnet4GenesisTx) `shouldBe` "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e"++{- Transactions signed by Bitcoin Core -}++-- | The vectors of data/core_signed_txs.json: the kind of coin, the signed+-- transaction in hex, and the output script in hex and the amount of the+-- coin that each of its two inputs spends+coreSignedVectors :: IO [(String, Text, Text, Word64)]+coreSignedVectors = readTestFileParser file "core_signed_txs.json"+ where+ file = withObject "file" $ \o -> o .: "vectors" >>= mapM vector+ vector = withObject "vector" $ \v -> (,,,) <$> v .: "kind" <*> v .: "tx" <*> v .: "script" <*> v .: "amount"++-- These signatures were made by Bitcoin Core 32.1 on regtest, not by the+-- library, so they check the verifier against an independent signer.+coreSignedSpec :: Ctx -> Spec+coreSignedSpec ctx =+ describe "Transactions signed by Bitcoin Core" $ do+ let verdict coinAmount (_, txHex, scriptHex, amount) = do+ tx <- decodeTx (hexBytes txHex)+ coin <- unmarshal ctx (hexBytes scriptHex)+ return (verifyStdTx btcRegTest ctx tx [(coin, coinAmount amount, i.outpoint) | i <- tx.inputs])+ kindOf (kind, _, _, _) = kind+ -- The kinds whose digest commits to the amount of the coin+ commitsToAmount v = any (`isPrefixOf` kindOf v) ["p2w", "p2sh-p2w", "p2tr"]++ -- 16 kinds of coin with the six hash types of ECDSA, and the default+ -- type as well for the Taproot key path: 16 × 6 + 1+ it "verifies the 97 spends of the file, of every kind of coin and hash type" $ do+ vectors <- coreSignedVectors+ length vectors `shouldBe` 97+ [kindOf v | v <- vectors, verdict id v /= Right True] `shouldBe` []++ -- 6 SegWit kinds with six hash types and the Taproot key path with seven+ it "refuses the 43 SegWit and Taproot spends of the file when the coin is given with one satoshi more" $ do+ vectors <- filter commitsToAmount <$> coreSignedVectors+ length vectors `shouldBe` 43+ [kindOf v | v <- vectors, verdict (+ 1) v /= Right False] `shouldBe` []++{- Inputs that no block can contain -}++-- A 2-of-3 multisig coin of the fixed keys 1, 2 and 3, in its three forms:+-- bare, pay-to-script-hash and pay-to-witness-script-hash. Each test gives+-- the signature items of the input, and gets the verdict for each form.+consensusSpec :: Ctx -> Spec+consensusSpec ctx =+ describe "Verification of inputs that no block can contain" $ do+ let (k1, p1) = fixedKey ctx 1+ (k2, p2) = fixedKey ctx 2+ (k3, p3) = fixedKey ctx 3+ (k4, _) = fixedKey ctx 4+ op = coinAt 0+ tx = buildTx ctx [op] [(PayPKHash (keyHash ctx p1), 90000)]+ ms = PayMulSig [p1, p2, p3] 2+ msScript = either error id (encodeOutput ctx ms)+ -- The digest that a signature of the bare and the pay-to-script-hash+ -- form signs, and the BIP 143 digest of the witness form+ legacyDigest = either error id (txSigHash btc tx msScript 100000 0 sigHashAll)+ witnessDigest = either error id (txSigHashForkId btc tx msScript 100000 0 sigHashAll)+ sig digest k = TxSignature (signHash ctx k.key digest) sigHashAll+ p2sh = either error id (toP2SH msScript)+ p2wsh = either error id (toP2WSH msScript)+ bare items = withInputScript tx 0 (marshal (btc, ctx) (RegularInput (SpendMulSig items)))+ hashed items = withInputScript tx 0 (marshal (btc, ctx) (ScriptHashInput (SpendMulSig items) ms))+ inWitness items = withWitness tx [simpleInputStack btc ctx (SpendMulSig items) <> [runPutS (serialize msScript)]]+ verdicts itemsOf =+ ( verifyStdInput btc ctx (bare (itemsOf legacyDigest)) [(ms, 100000, op)] 0,+ verifyStdInput btc ctx (hashed (itemsOf legacyDigest)) [(p2sh, 100000, op)] 0,+ verifyStdInput btc ctx (inWitness (itemsOf witnessDigest)) [(p2wsh, 100000, op)] 0+ )++ it "accepts a 2-of-3 multisig input with the signatures of two of its keys, in key order" $ do+ verdicts (\d -> [sig d k1, sig d k2]) `shouldBe` (True, True, True)+ verdicts (\d -> [sig d k1, sig d k3]) `shouldBe` (True, True, True)++ -- OP_CHECKMULTISIG takes as many signatures as the script requires. An+ -- item more becomes its dummy element, which must be empty.+ it "refuses a 2-of-3 multisig input with a third item after its two signatures" $ do+ verdicts (\d -> [sig d k1, sig d k2, sig d k4]) `shouldBe` (False, False, False)+ verdicts (\d -> [sig d k1, sig d k2, sig d k3]) `shouldBe` (False, False, False)++ -- The two items taken as signatures are the empty one and the last.+ -- The first signature is left in the place of the dummy element.+ it "refuses a 2-of-3 multisig input with an empty item among its signatures" $ do+ verdicts (\d -> [sig d k1, TxSignatureEmpty, sig d k3]) `shouldBe` (False, False, False)+ verdicts (\d -> [sig d k1, TxSignatureEmpty]) `shouldBe` (False, False, False)++ -- The empty item becomes the dummy element, and the one written for+ -- that stays on the stack. A witness must leave a clean stack (BIP+ -- 141). The legacy forms may be in a block, but nodes do not relay them+ -- (CLEANSTACK), and the verifier refuses what is not relayed.+ it "refuses a 2-of-3 multisig input with an empty item before its two signatures" $+ verdicts (\d -> [TxSignatureEmpty, sig d k1, sig d k2]) `shouldBe` (False, False, False)++ it "refuses a 2-of-3 multisig input with one signature, or with two out of key order" $ do+ verdicts (\d -> [sig d k1]) `shouldBe` (False, False, False)+ verdicts (\d -> [sig d k3, sig d k1]) `shouldBe` (False, False, False)++ -- BIP 141: the input script of a nested witness program "must be+ -- exactly a push of the BIP16 redeemScript"+ it "refuses a P2SH-P2WPKH input whose script pushes the program with OP_PUSHDATA1" $ do+ let h = keyHash ctx p1+ coin = either error id (toP2SH =<< encodeOutput ctx (PayWitnessPKHash h))+ sigi = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing+ program = B.pack [0x00, 0x14] <> runPutS (serialize h)+ signed <- either fail return (signNestedWitnessTx btc ctx tx [sigi] [k1.key])+ (head signed.inputs).script `shouldBe` B.singleton 0x16 <> program+ verifyStdInput btc ctx signed [(coin, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx (withInputScript signed 0 (B.pack [0x4c, 0x16] <> program)) [(coin, 100000, op)] 0 `shouldBe` False++ it "refuses a Taproot input that has an input script" $ do+ let so = taprootScriptOutput ctx (TaprootOutput p1.point Nothing)+ signed <- either fail return (signTx btc ctx tx [SigInput so 100000 op sigHashDefault Nothing] [k1.key])+ verifyStdInput btc ctx signed [(so, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx (withInputScript signed 0 "\x51") [(so, 100000, op)] 0 `shouldBe` False++ -- CheckTransaction of Bitcoin Core: bad-txns-inputs-duplicate+ it "refuses a transaction that lists the same outpoint twice" $ do+ let h = keyHash ctx p1+ twice = buildTx ctx [op, op] [(PayPKHash h, 90000)]+ sigi = SigInput (PayPKHash h) 100000 op sigHashAll Nothing+ signed <- either fail return (signTx btc ctx twice [sigi, sigi] [k1.key])+ map (B.null . (.script)) signed.inputs `shouldBe` [False, False]+ verifyStdTx btc ctx signed [(PayPKHash h, 100000, op)] `shouldBe` False+ verifyStdTx btc ctx signed [(PayPKHash h, 100000, op), (PayPKHash h, 100000, op)] `shouldBe` False++ it "refuses a transaction with no inputs and no outputs" $+ verifyStdTx btc ctx (Tx 1 [] [] [] 0) [] `shouldBe` False++ it "accepts outputs that spend exactly what the coins hold, and refuses one satoshi more" $ do+ let h = keyHash ctx p1+ sigi = SigInput (PayPKHash h) 100000 op sigHashAll Nothing+ exact = buildTx ctx [op] [(PayPKHash h, 100000)]+ over = buildTx ctx [op] [(PayPKHash h, 100001)]+ signedExact <- either fail return (signTx btc ctx exact [sigi] [k1.key])+ signedOver <- either fail return (signTx btc ctx over [sigi] [k1.key])+ verifyStdTx btc ctx signedExact [(PayPKHash h, 100000, op)] `shouldBe` True+ verifyStdTx btc ctx signedOver [(PayPKHash h, 100000, op)] `shouldBe` False++ -- Bitcoin Cash has no SegWit, so a witness program is no template there+ it "refuses a witness spend on a network without SegWit" $ do+ let h = keyHash ctx p1+ forkId = setForkIdFlag sigHashAll+ code = either error id (encodeOutput ctx (PayPKHash h))+ digest = either error id (txSigHashForkId bch tx code 100000 0 forkId)+ stack = [encodeTxSig bch ctx (TxSignature (signHash ctx k1.key digest) forkId), marshal ctx p1]+ verifyStdInput bch ctx (withWitness tx [stack]) [(PayWitnessPKHash h, 100000, op)] 0 `shouldBe` False++{- A coin spent with a key or a script that is not its own -}++-- In each test the thief signs the right digest of the coin with key 2, and+-- puts key 2, or a script of key 2, where the coin commits to key 1.+thiefSpec :: Ctx -> Spec+thiefSpec ctx =+ describe "Verification of a coin spent with a key or a script that is not its own" $ do+ let (k1, p1) = fixedKey ctx 1+ (k2, p2) = fixedKey ctx 2+ h1 = keyHash ctx p1+ h2 = keyHash ctx p2+ op = coinAt 0+ tx = buildTx ctx [op] [(PayPKHash h1, 90000)]+ script = either error id . encodeOutput ctx+ legacy code = either error id (txSigHash btc tx (script code) 100000 0 sigHashAll)+ bip143 code = either error id (txSigHashForkId btc tx (script code) 100000 0 sigHashAll)+ sig digest k = TxSignature (signHash ctx k.key digest) sigHashAll+ owner sigi = either error id (signTx btc ctx tx [sigi] [k1.key])+ ownerNested sigi = either error id (signNestedWitnessTx btc ctx tx [sigi] [k1.key])++ it "refuses a P2PKH coin spent with another key" $ do+ let coin = PayPKHash h1+ thief = withInputScript tx 0 (marshal (btc, ctx) (RegularInput (SpendPKHash (sig (legacy coin) k2) p2)))+ verifyStdInput btc ctx (owner (SigInput coin 100000 op sigHashAll Nothing)) [(coin, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx thief [(coin, 100000, op)] 0 `shouldBe` False++ it "refuses a P2SH coin spent with another redeem script" $ do+ let coin = either error id (toP2SH (script (PayPK p1)))+ thief = withInputScript tx 0 (marshal (btc, ctx) (ScriptHashInput (SpendPK (sig (legacy (PayPK p2)) k2)) (PayPK p2)))+ verifyStdInput btc ctx (owner (SigInput coin 100000 op sigHashAll (Just (PayPK p1)))) [(coin, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx thief [(coin, 100000, op)] 0 `shouldBe` False++ it "refuses a P2WPKH coin spent with another key" $ do+ let coin = PayWitnessPKHash h1+ thief = withWitness tx [[encodeTxSig btc ctx (sig (bip143 (PayPKHash h1)) k2), marshal ctx p2]]+ verifyStdInput btc ctx (owner (SigInput coin 100000 op sigHashAll Nothing)) [(coin, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx thief [(coin, 100000, op)] 0 `shouldBe` False++ it "refuses a P2WSH coin spent with another witness script" $ do+ let own = PayMulSig [p1] 1+ other = PayMulSig [p2] 1+ coin = either error id (toP2WSH (script own))+ thief = withWitness tx [["", encodeTxSig btc ctx (sig (bip143 other) k2), runPutS (serialize (script other))]]+ verifyStdInput btc ctx (owner (SigInput coin 100000 op sigHashAll (Just own))) [(coin, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx thief [(coin, 100000, op)] 0 `shouldBe` False++ it "refuses a P2SH-P2WPKH coin spent with the program of another key" $ do+ let coin = either error id (toP2SH (script (PayWitnessPKHash h1)))+ thief =+ withWitness+ (withInputScript tx 0 (marshal (btc, ctx) (NestedWitnessInput (PayWitnessPKHash h2))))+ [[encodeTxSig btc ctx (sig (bip143 (PayPKHash h2)) k2), marshal ctx p2]]+ verifyStdInput btc ctx (ownerNested (SigInput (PayWitnessPKHash h1) 100000 op sigHashAll Nothing)) [(coin, 100000, op)] 0 `shouldBe` True+ verifyStdInput btc ctx thief [(coin, 100000, op)] 0 `shouldBe` False++{- What the signer refuses -}++signerSpec :: Ctx -> Spec+signerSpec ctx =+ describe "What the signer refuses" $ do+ let (k1, p1) = fixedKey ctx 1+ (k2, _) = fixedKey ctx 2+ (k1u, p1u) = fixedKeyU ctx 1+ h1 = keyHash ctx p1+ op = coinAt 0+ -- Two outputs, so that SIGHASH_SINGLE has one at the index of the input+ tx = buildTx ctx [op] [(PayPKHash h1, 50000), (PayPKHash h1, 40000)]+ signWith net coin sh = signTx net ctx tx [SigInput coin 100000 op sh Nothing] [k1.key]++ it "signs the six hash types that a node accepts" $+ forM_ [0x01, 0x02, 0x03, 0x81, 0x82, 0x83] $ \sh -> do+ signWith btc (PayPKHash h1) sh `shouldSatisfy` isRight+ signWith btc (PayWitnessPKHash h1) sh `shouldSatisfy` isRight++ -- IsDefinedHashtypeSignature of Bitcoin Core: without the+ -- SIGHASH_ANYONECANPAY bit the byte is 1, 2 or 3+ it "refuses a hash type that no node accepts on Bitcoin" $+ forM_ [PayPKHash h1, PayWitnessPKHash h1] $ \coin -> do+ signWith btc coin 0x00 `shouldBe` Left "makeSignature: the hash type 0 is not one a node accepts"+ signWith btc coin 0x04 `shouldBe` Left "makeSignature: the hash type 4 is not one a node accepts"+ signWith btc coin 0x21 `shouldBe` Left "makeSignature: the hash type 33 is not one a node accepts"+ signWith btc coin 0x80 `shouldBe` Left "makeSignature: the hash type 128 is not one a node accepts"+ signWith btc coin 0xff `shouldBe` Left "makeSignature: the hash type 255 has the fork id flag, which this network does not use"+ signWith btc coin 0x41 `shouldBe` Left "makeSignature: the hash type 65 has the fork id flag, which this network does not use"++ -- A signature carries one byte of hash type, and the digest four+ it "refuses a hash type that does not fit in one byte" $ do+ signWith btc (PayPKHash h1) 0x101 `shouldBe` Left "makeSignature: the hash type 257 does not fit in the one byte of a signature"+ signWith btc (PayWitnessPKHash h1) 0x101 `shouldBe` Left "makeSignature: the hash type 257 does not fit in the one byte of a signature"+ signWith bch (PayPKHash h1) 0x101 `shouldBe` Left "makeSignature: the hash type 257 does not fit in the one byte of a signature"++ -- 0x20 is SIGHASH_UTXOS on Bitcoin Cash, whose digest the library does+ -- not compute+ it "refuses a hash type that it cannot sign on Bitcoin Cash, and adds the fork id flag to the others" $ do+ signWith bch (PayPKHash h1) 0x04 `shouldBe` Left "makeSignature: the hash type 4 is not one a node accepts"+ signWith bch (PayPKHash h1) 0x21 `shouldBe` Left "makeSignature: the hash type 33 is not one a node accepts"+ signed <- either fail return (signWith bch (PayPKHash h1) 0x01)+ -- The signature is the first push of the input script, and its last byte the hash type+ fmap (\si -> case si of RegularInput (SpendPKHash (TxSignature _ sh) _) -> Just sh; _ -> Nothing) (unmarshal (bch, ctx) (head signed.inputs).script)+ `shouldBe` Right (Just 0x41)++ it "refuses a pay-to-script-hash coin whose redeem script is over 520 bytes" $ do+ let rdm = PayMulSig [snd (fixedKey ctx n) | n <- [1 .. 16]] 1+ -- OP_1, 16 keys of 34 bytes with their push, OP_16, OP_CHECKMULTISIG: 547 bytes+ h = addressHash (runPutS (serialize (either error id (encodeOutput ctx rdm))))+ signTx btc ctx tx [SigInput (PayScriptHash h) 100000 op sigHashAll (Just rdm)] [k1.key]+ `shouldBe` Left "The redeem script is 547 bytes long, and a pay-to-script-hash redeem script can have at most 520"++ it "refuses a pay-to-witness-script-hash coin whose witness script is over 10000 bytes" $ do+ let rdm = DataCarrier [B.replicate 10000 0]+ -- OP_RETURN, OP_PUSHDATA2 with its two length bytes, 10000 bytes: 10004+ h = sha256 (runPutS (serialize (either error id (encodeOutput ctx rdm))))+ makeSignature btc ctx tx 0 (SigInput (PayWitnessScriptHash h) 100000 op sigHashAll (Just rdm)) k1+ `shouldBe` Left "The witness script is 10004 bytes long, and a pay-to-witness-script-hash witness script can have at most 10000"++ it "refuses a redeem script or a witness script that does not hash to the coin, and a coin without its script" $ do+ let other = "0000000000000000000000000000000000000000" :: Hash160+ other32 = "0000000000000000000000000000000000000000000000000000000000000000" :: Hash256+ rdm = PayPK p1+ makeSignature btc ctx tx 0 (SigInput (PayScriptHash other) 100000 op sigHashAll (Just rdm)) k1+ `shouldBe` Left "makeSignature: the redeem script does not hash to the coin"+ makeSignature btc ctx tx 0 (SigInput (PayWitnessScriptHash other32) 100000 op sigHashAll (Just rdm)) k1+ `shouldBe` Left "makeSignature: the witness script does not hash to the coin"+ makeSignature btc ctx tx 0 (SigInput (PayScriptHash other) 100000 op sigHashAll Nothing) k1+ `shouldBe` Left "makeSignature: a pay-to-script-hash coin needs its redeem script"+ makeSignature btc ctx tx 0 (SigInput (PayWitnessScriptHash other32) 100000 op sigHashAll Nothing) k1+ `shouldBe` Left "makeSignature: a pay-to-witness-script-hash coin needs its witness script"++ -- SCRIPT_VERIFY_WITNESS_PUBKEYTYPE of Bitcoin Core: no node relays a+ -- SegWit version 0 spend with an uncompressed key+ it "signTx refuses a SegWit coin of an uncompressed key, native, in a witness script or nested" $ do+ let hu = keyHash ctx p1u+ refusal = Left "sigKeys: a SegWit input is signed with a compressed key only, and this coin pays to an uncompressed key"+ wsh rdm = PayWitnessScriptHash (sha256 (runPutS (serialize (either error id (encodeOutput ctx rdm)))))+ signTx btc ctx tx [SigInput (PayWitnessPKHash hu) 100000 op sigHashAll Nothing] [k1.key] `shouldBe` refusal+ signNestedWitnessTx btc ctx tx [SigInput (PayWitnessPKHash hu) 100000 op sigHashAll Nothing] [k1.key] `shouldBe` refusal+ signTx btc ctx tx [SigInput (wsh (PayPK p1u)) 100000 op sigHashAll (Just (PayPK p1u))] [k1.key] `shouldBe` refusal+ signTx btc ctx tx [SigInput (wsh (PayMulSig [p1u] 1)) 100000 op sigHashAll (Just (PayMulSig [p1u] 1))] [k1.key] `shouldBe` refusal+ -- The same key signs a legacy coin in its uncompressed form+ signTx btc ctx tx [SigInput (PayPKHash hu) 100000 op sigHashAll Nothing] [k1.key] `shouldSatisfy` isRight++ -- A block accepts such a spend, so one who knows what it does can still+ -- make it, with the key in the form to use+ it "signInput signs a SegWit coin with the uncompressed key it is given" $ do+ let hu = keyHash ctx p1u+ sigi = SigInput (PayWitnessPKHash hu) 100000 op sigHashAll Nothing+ signed <- either fail return (signInput btc ctx tx 0 sigi k1u)+ map (map B.length) signed.witness `shouldSatisfy` (\w -> map (drop 1) w == [[65]])+ verifyStdInput btc ctx signed [(PayWitnessPKHash hu, 100000, op)] 0 `shouldBe` True++ it "refuses to sign a SegWit or a Taproot coin on a network without SegWit" $ do+ let so = taprootScriptOutput ctx (TaprootOutput p1.point Nothing)+ signWith bch (PayWitnessPKHash h1) sigHashAll `shouldBe` Left "makeSignature: this network has no SegWit"+ signTx bch ctx tx [SigInput so 100000 op sigHashDefault Nothing] [k1.key] `shouldBe` Left "signTx: this network has no Taproot"++ it "signInput refuses a key that is not a key of the coin" $ do+ signInput btc ctx tx 0 (SigInput (PayPKHash h1) 100000 op sigHashAll Nothing) k2 `shouldBe` Left "signInput: the key is not a key of the coin"+ signInput btc ctx tx 0 (SigInput (PayWitnessPKHash h1) 100000 op sigHashAll Nothing) k2 `shouldBe` Left "signInput: the key is not a key of the coin"+ signInput btc ctx tx 0 (SigInput (PayMulSig [p1] 1) 100000 op sigHashAll Nothing) k2 `shouldBe` Left "signInput: the key is not a key of the coin"+ signInput btc ctx tx 0 (SigInput (PayPKHash h1) 100000 op sigHashAll Nothing) k1 `shouldSatisfy` isRight++ it "signTaprootInput refuses a key that is not the key of the output, and an output that is not Taproot" $ do+ let so = taprootScriptOutput ctx (TaprootOutput p1.point Nothing)+ spent = [TxOut 100000 (marshal ctx so)]+ tweaked = fromJust (taprootOutputSecKey ctx k1.key Nothing)+ -- The internal key is not the key of the output: its tweak is+ signTaprootInput ctx tx spent 0 sigHashDefault k1.key `shouldBe` Left "signTaprootInput: the key is not the key of the output"+ signTaprootInput ctx tx spent 0 sigHashDefault tweaked `shouldSatisfy` isRight+ signTaprootInput ctx tx [TxOut 100000 (marshal ctx (PayWitnessPKHash h1))] 0 sigHashDefault tweaked+ `shouldBe` Left "signTaprootInput: the output spent is not a Taproot output"++ it "refuses to sign a SegWit input of a transaction whose witness list does not match its inputs" $ do+ let two = buildTx ctx [op, coinAt 1] [(PayPKHash h1, 90000)]+ sigi = SigInput (PayWitnessPKHash h1) 100000 (coinAt 1) sigHashAll Nothing+ ms = PayMulSig [p1] 1+ wsh = either error id (toP2WSH =<< encodeOutput ctx ms)+ signTx btc ctx (withWitness two [[]]) [sigi] [k1.key] `shouldBe` Left "Invalid number of witness stacks"+ signTx btc ctx (withWitness two [[]]) [SigInput wsh 100000 (coinAt 1) sigHashAll (Just ms)] [k1.key] `shouldBe` Left "Invalid number of witness stacks"+ signTx btc ctx two [sigi] [k1.key] `shouldSatisfy` isRight
test/Haskoin/UtilSpec.hs view
@@ -9,6 +9,7 @@ module Haskoin.UtilSpec (spec) where import Control.Exception (ErrorCall, evaluate)+import System.Timeout (timeout) import Control.Monad.Except (ExceptT, runExceptT) import Data.Aeson import Data.Aeson.Encoding (encodingToLazyByteString)@@ -23,7 +24,7 @@ import Data.Foldable (toList) import Data.Int (Int32, Int64) import Data.IntMap qualified as IntMap-import Data.List (permutations)+import Data.List (isInfixOf, permutations) import Data.Maybe import Data.Sequence qualified as Seq import Data.Serialize qualified as S@@ -80,7 +81,7 @@ bsToInteger "\x01\x00" `shouldBe` 256 bsToInteger "\x00\x00\x01" `shouldBe` 1 bsToInteger "" `shouldBe` 0- prop "bsToInteger . integerToBS" getPutInteger+ prop "bsToInteger . integerToBS gives back a number that is not negative" bytesOfNonNegative describe "Hex" $ do it "encodes lower case and decodes either case" $ do@@ -171,6 +172,20 @@ 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"+ it "refuses a digit that does not fit the width it is converted from" $+ -- 256 needs nine bits. The BIP 173 reference returns None here.+ convertBits True 8 5 [256] `shouldBe` Nothing+ it "refuses a width that is zero, negative or too wide for a Word, instead of looping" $ do+ -- The comparison is made inside the time limit, so that a result+ -- that never ends cannot be forced outside it.+ let refused frombits tobits digits =+ timeout 100000 (evaluate (convertBits True frombits tobits digits == Nothing))+ refused 8 0 [1] `shouldReturn` Just True+ refused 8 (-1) [1] `shouldReturn` Just True+ refused 0 8 [1] `shouldReturn` Just True+ refused (-8) 5 [1] `shouldReturn` Just True+ -- 60 + 8 bits do not fit the 64 bits of the accumulator+ refused 60 8 [1] `shouldReturn` Just True 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)))@@ -206,9 +221,46 @@ 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)+ -- A number has one encoding, the one putInteger writes. The cases+ -- below are other ways to write a number that getInteger used to read.+ it "getInteger refuses a tag byte other than 0 and 1" $+ -- tag 2, then sign 0, length 1 and the byte 5+ runGetS getInteger "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x05"+ `shouldSatisfy` either ("tag" `isInfixOf`) (const False)+ it "getInteger refuses a sign byte other than 0x01 and 0xff" $ do+ -- 2^31 with the sign bytes 0x00 and 0x02+ runGetS getInteger "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x80"+ `shouldSatisfy` either ("sign" `isInfixOf`) (const False)+ runGetS getInteger "\x01\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x80"+ `shouldSatisfy` either ("sign" `isInfixOf`) (const False)+ it "getInteger refuses a wide number with zero bytes at its top" $+ -- 2^31 in five bytes, little-endian, the fifth being zero+ runGetS getInteger "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x80\x00"+ `shouldSatisfy` either ("shortest" `isInfixOf`) (const False)+ it "getInteger refuses the wide form of a number that fits 32 bits" $ do+ -- 5, and -2^31, which both fit the form with a zero tag+ runGetS getInteger "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x05"+ `shouldSatisfy` either ("shortest" `isInfixOf`) (const False)+ runGetS getInteger "\x01\xff\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x80"+ `shouldSatisfy` either ("shortest" `isInfixOf`) (const False)+ it "getInteger refuses a wide number of no bytes" $+ runGetS getInteger "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00"+ `shouldSatisfy` either ("shortest" `isInfixOf`) (const False)+ it "getInteger reads the smallest 32-bit number, -2^31" $+ runGetS getInteger "\x00\x80\x00\x00\x00" `shouldBe` Right (-2147483648)+ it "getInteger reads a number of 200,000 bytes in well under two seconds" $ do+ -- 256^200000 - 1: every byte is 0xff. The old reader took 11 seconds.+ let input = "\x01\x01\x00\x00\x00\x00\x00\x03\x0d\x40" <> B.replicate 200000 0xff+ expected = 256 ^ (200000 :: Int) - 1 :: Integer+ timeout 2000000 (evaluate (runGetS getInteger input == Right expected))+ `shouldReturn` Just True 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+ -- Versions before 0.20 wrote the chain work of a header with the+ -- Integer instance of cereal. Headers stored then must still read.+ prop "getInteger reads what the Integer instance of cereal writes" $ \(i :: Integer) (k :: Int) ->+ let n = i * 2 ^ (abs k `mod` 256) in runGetS getInteger (S.encode 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"@@ -223,6 +275,13 @@ 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 "getIntMap refuses keys that are not in ascending order, such as a key given twice" $ do+ -- two pairs with the key 1+ runGetS (getIntMap (fromIntegral <$> getWord8) getWord8) "\x00\x00\x00\x00\x00\x00\x00\x02\x01\x0a\x01\x0b"+ `shouldSatisfy` either ("ascending" `isInfixOf`) (const False)+ -- the keys 2 then 1+ runGetS (getIntMap (fromIntegral <$> getWord8) getWord8) "\x00\x00\x00\x00\x00\x00\x00\x02\x02\x14\x01\x0a"+ `shouldSatisfy` either ("ascending" `isInfixOf`) (const False) 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)@@ -234,6 +293,13 @@ 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 "unmarshal refuses bytes after the value, strict and lazy" $ do+ -- the compressed public key of secret key 1, then one more byte+ let bytes = fromJust (decodeHex "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff")+ (unmarshal ctx bytes :: Either String PublicKey)+ `shouldSatisfy` either ("bytes left over" `isInfixOf`) (const False)+ (unmarshalLazy ctx (BL.fromStrict bytes) :: Either String PublicKey)+ `shouldSatisfy` either ("bytes left over" `isInfixOf`) (const False) 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@@ -255,8 +321,9 @@ type ExceptTString a = ExceptT String IO a -getPutInteger :: Integer -> Bool-getPutInteger i = bsToInteger (integerToBS $ abs i) == abs i+-- | integerToBS takes no negative number, so the absolute value is tested.+bytesOfNonNegative :: Integer -> Bool+bytesOfNonNegative i = bsToInteger (integerToBS $ abs i) == abs i fromToHex :: ByteString -> Bool fromToHex bs = decodeHex (encodeHex bs) == Just bs