{-# LANGUAGE OverloadedStrings #-} module Haskoin.Crypto.SignatureSpec (spec) where import Control.Monad import Data.Bits (testBit) import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe import Data.Serialize as S import Data.String.Conversions (cs) import Data.Text (Text) import Haskoin.Address import Haskoin.Transaction import Haskoin.Keys import Haskoin.Crypto import Haskoin.Script import Haskoin.Constants import Haskoin.Util import Haskoin.Util.Arbitrary import Haskoin.UtilSpec (readTestFile) import Test.Hspec import Test.Hspec.QuickCheck import Test.HUnit import Test.QuickCheck spec :: Spec spec = do describe "Signature properties" $ do prop "verify signature" $ forAll arbitrarySignature $ \(m, key', sig) -> verifyHashSig m sig (derivePubKey key') prop "s component less than half order" $ forAll arbitrarySignature $ isCanonicalHalfOrder . lst3 prop "encoded signature is canonical" $ forAll arbitrarySignature $ testIsCanonical . lst3 prop "decodeStrictSig . exportSig identity" $ forAll arbitrarySignature $ (\s -> decodeStrictSig (exportSig s) == Just s) . lst3 prop "importSig . exportSig identity" $ forAll arbitrarySignature $ (\s -> importSig (exportSig s) == Just s) . lst3 prop "getSig . putSig identity" $ forAll arbitrarySignature $ (\s -> runGet getSig (runPut $ putSig s) == Right s) . lst3 describe "Signature vectors" $ checkDistSig $ \file1 file2 -> do vectors <- runIO (readTestFile file1 :: IO [(Text, Text, Text)]) vectorsDER <- runIO (readTestFile file2 :: IO [(Text, Text, Text)]) it "Passes the trezor rfc6979 test vectors" $ mapM_ (testRFC6979Vector . toVector) vectors it "Passes the rfc6979 DER test vectors" $ mapM_ (testRFC6979DERVector . toVector) vectorsDER describe "BIP143 signature vectors" $ do it "agrees with BIP143 p2wpkh example" testBip143p2wpkh it "agrees with BIP143 p2sh-p2wpkh example" testBip143p2shp2wpkh it "builds a p2wsh multisig transaction" testP2WSHMulsig it "agrees with BIP143 p2sh-p2wsh multisig example" testBip143p2shp2wpkhMulsig -- github.com/bitcoin/bitcoin/blob/master/src/script.cpp -- from function IsCanonicalSignature testIsCanonical :: Sig -> Bool testIsCanonical sig = not $ -- Non-canonical signature: too short (len < 8) || -- Non-canonical signature: too long (len > 72) || -- Non-canonical signature: wrong type (BS.index s 0 /= 0x30) || -- Non-canonical signature: wrong length marker (BS.index s 1 /= len - 2) || -- Non-canonical signature: S length misplaced (5 + rlen >= len) || -- Non-canonical signature: R+S length mismatch (rlen + slen + 6 /= len) || -- Non-canonical signature: R value type mismatch (BS.index s 2 /= 0x02) || -- Non-canonical signature: R length is zero (rlen == 0) || -- Non-canonical signature: R value negative testBit (BS.index s 4) 7 || -- Non-canonical signature: R value excessively padded ( rlen > 1 && BS.index s 4 == 0 && not (testBit (BS.index s 5) 7) ) || -- Non-canonical signature: S value type mismatch (BS.index s (fromIntegral rlen + 4) /= 0x02) || -- Non-canonical signature: S length is zero (slen == 0) || -- Non-canonical signature: S value negative testBit (BS.index s (fromIntegral rlen+6)) 7 || -- Non-canonical signature: S value excessively padded ( slen > 1 && BS.index s (fromIntegral rlen + 6) == 0 && not (testBit (BS.index s (fromIntegral rlen + 7)) 7) ) where s = exportSig sig len = fromIntegral $ BS.length s rlen = BS.index s 3 slen = BS.index s (fromIntegral rlen + 5) -- RFC6979 note: Different libraries of libsecp256k1 use different constants -- to produce a nonce. Thus, their deterministric signatures will be different. -- We still want to test against fixed signatures so we need a way to switch -- between implementations. We check the output of signMsg 1 0 data ValidImpl = ImplCore | ImplABC implSig :: Text implSig = encodeHex $ exportSig $ signMsg "0000000000000000000000000000000000000000000000000000000000000001" "0000000000000000000000000000000000000000000000000000000000000000" -- We have test vectors for these cases validImplMap :: Map Text ValidImpl validImplMap = Map.fromList [ ( "3045022100a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231\ \d9e7e2e1e4022011edc8d307254296264aebfc3dc76cd8b668373a072fd64665\ \b50000e9fcce52" , ImplCore) , ( "304402200581361d23e645be9e3efe63a9a2ac2e8dd0c70ba3ac8554c9befe06\ \0ad0b36202207d8172f1e259395834793d81b17e986f1e6131e4734969d2f4ae\ \3a9c8bc42965" , ImplABC) ] getImpl :: Maybe ValidImpl getImpl = implSig `Map.lookup` validImplMap rfc6979files :: ValidImpl -> (FilePath, FilePath) rfc6979files ImplCore = ("rfc6979core.json", "rfc6979DERcore.json") rfc6979files ImplABC = ("rfc6979abc.json", "rfc6979DERabc.json") checkDistSig :: (FilePath -> FilePath -> Spec) -> Spec checkDistSig go = case rfc6979files <$> getImpl of Just (file1, file2) -> go file1 file2 _ -> it "Passes rfc6979 test vectors" $ void $ assertFailure "Invalid rfc6979 signature" {- Trezor RFC 6979 Test Vectors -} -- github.com/trezor/python-ecdsa/blob/master/ecdsa/test_pyecdsa.py toVector :: (Text, Text, Text) -> (SecKey, ByteString, Text) toVector (prv, m, res) = (fromJust $ (secKey <=< decodeHex) prv, cs m, res) testRFC6979Vector :: (SecKey, ByteString, Text) -> Assertion testRFC6979Vector (prv, m, res) = do assertEqual "RFC 6979 Vector" res (encodeHex $ encode $ exportCompactSig s) assertBool "Signature is valid" $ verifyHashSig h s (derivePubKey prv) assertBool "Signature is canonical" $ testIsCanonical s assertBool "Signature is normalized" $ isCanonicalHalfOrder s where h = sha256 m s = signHash prv h -- Test vectors from: -- https://crypto.stackexchange.com/questions/20838/request-for-data-to-test-deterministic-ecdsa-signature-algorithm-for-secp256k1 testRFC6979DERVector :: (SecKey, ByteString, Text) -> Assertion testRFC6979DERVector (prv, m, res) = do assertEqual "RFC 6979 DER Vector" res (encodeHex $ exportSig s) assertBool "DER Signature is valid" $ verifyHashSig h s (derivePubKey prv) assertBool "DER Signature is canonical" $ testIsCanonical s assertBool "DER Signature is normalized" $ isCanonicalHalfOrder s where h = sha256 m s = signHash prv h -- Reproduce the P2WPKH example from BIP 143 testBip143p2wpkh :: Assertion testBip143p2wpkh = case getImpl of Just ImplCore -> assertEqual "BIP143 Core p2wpkh" (Right signedTxCore) generatedSignedTx Just ImplABC -> assertEqual "BIP143 ABC p2wpkh" (Right signedTxABC) generatedSignedTx Nothing -> assertFailure "Invalid secp256k1 library" where signedTxCore = "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433\ \541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742f\ \a9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1\ \c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89\ \d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffff\ \ffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac\ \7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0\ \167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32\ \a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f33\ \58f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e\ \7acafcdb3566bb0ad253f62fc70f07aeee635711000000" signedTxABC = "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433\ \541db4e4ad969f000000004847304402200fbc9dad97500334e47c2dca50096a\ \2117c01952c2870102e320823d21c36229022007cb36c2b141d11c08ef81d948\ \f148332fc09fe8f6d226aaaf8ba6ae0d8a66ba01eeffffffef51e1b804cc89d1\ \82d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffff\ \ff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a\ \6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f016\ \7faa815988ac0002473044022011cb891cee521eb1fc7aef681655a881288553\ \fc024cff9cee5007bae5e6b8c602200b89d60ee2f98aa9a645dad59cd680b4b6\ \25f343efcd3e7fb70852100ef601890121025476c2e83188368da1ff3e292e7a\ \cafcdb3566bb0ad253f62fc70f07aeee635711000000" unsignedTx = "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541d\ \b4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b\ \1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb20600000000\ \1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d0000\ \00001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000" Just key0 = secHexKey "bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866" pubKey0 = toPubKey key0 Just key1 = secHexKey "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9" [op0, op1] = prevOutput <$> txIn unsignedTx sigIn0 = SigInput (PayPK pubKey0) 625000000 op0 sigHashAll Nothing WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey key1 sigIn1 = SigInput (PayWitnessPKHash h) 600000000 op1 sigHashAll Nothing generatedSignedTx = signTx btc unsignedTx [sigIn0, sigIn1] [key0, key1] -- Reproduce the P2SH-P2WPKH example from BIP 143 testBip143p2shp2wpkh :: Assertion testBip143p2shp2wpkh = case getImpl of Just ImplCore -> assertEqual "BIP143 Core p2sh-p2wpkh" (Right signedTxCore) generatedSignedTx Just ImplABC -> assertEqual "BIP143 ABC p2sh-p2wpkh" (Right signedTxABC) generatedSignedTx Nothing -> assertFailure "Invalid secp256k1 library" where signedTxCore = "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\ \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\ \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\ \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\ \a7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a1\ \0d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d\ \877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c7\ \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000" signedTxABC = "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\ \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\ \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\ \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\ \a7ad0402e8bd8ad6d77c88ac024730440220091c78fd1e21535f6ddc45515e4c\ \afca15cdf344765d72c1529fb82d3ada2d1802204a980d5e37d0b04f5e1185a0\ \f97295c383764e9a4b08d8bd1161b33c6719139a012103ad1d8e89212f0b92c7\ \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000" unsignedTx = "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d\ \3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7\ \f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b\ \1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000" Just key0 = secHexKey "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf" op0 = prevOutput . head $ txIn unsignedTx WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey key0 sigIn0 = SigInput (PayWitnessPKHash h) 1000000000 op0 sigHashAll Nothing generatedSignedTx = signNestedWitnessTx btc unsignedTx [sigIn0] [key0] -- P2WSH multisig example (tested against bitcoin-core 0.19.0.1) testP2WSHMulsig :: Assertion testP2WSHMulsig = case getImpl of Just ImplCore -> assertEqual "Core p2wsh multisig" (Right signedTxCore) generatedSignedTx Just ImplABC -> assertEqual "ABC p2wsh multisig" (Right signedTxABC) generatedSignedTx Nothing -> assertFailure "Invalid secp256k1 library" where signedTxCore = "01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f8961\ \34a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a35\ \2cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100fad4fedd2b\ \b4c439c64637eb8e9150d9020a7212808b8dc0578d5ff5b4ad65fe0220714640\ \f261b37eb3106310bf853f4b706e51436fb6b64c2ab00768814eb55b98014730\ \44022100baff4e4ceea4022b9725a2e6f6d77997a554f858165b91ac8c16c983\ \3008bee9021f5f70ebc3f8580dc0a5e96451e3697bdf1f1f5883944f0f33ab0c\ \fb272354040169522102ba46d3bb8db74c77c6cf082db57fc0548058fcdea811\ \549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f69e\ \a311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695e2\ \d8e007a7f26db96c2ee42db15dc953ae00000000" signedTxABC = "01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f8961\ \34a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a35\ \2cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100b79bf3714a\ \50f8f0e2f946034361ba4f6567b796d55910d89e98720d2e99f98c0220134879\ \518002df23e80a058475fa8b10bc4182bedfecd5f85e446a00f211ea53014830\ \45022100ce3c77480d664430a7544c1a962d1ae31151109a528a37e5bccc92ba\ \2e460ad10220317bc9a71d0c3471058d16d4c3b1ea99616208db6b9b9040fb81\ \0a7fa27f72b40169522102ba46d3bb8db74c77c6cf082db57fc0548058fcdea8\ \11549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f6\ \9ea311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695\ \e2d8e007a7f26db96c2ee42db15dc953ae00000000" unsignedTx = "0100000001d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f896134a4\ \48c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a352cab\ \583b12fbcb26d1269b4a2c951a33ad88ac00000000" op0 = head $ prevOutput <$> txIn unsignedTx Just keys = traverse secHexKey [ "3030303030303030303030303030303030303030303030303030303030303031" , "3030303030303030303030303030303030303030303030303030303030303032" , "3030303030303030303030303030303030303030303030303030303030303033" ] rdm = PayMulSig (toPubKey <$> keys) 2 sigIn = SigInput (toP2WSH $ encodeOutput rdm) 100000000 op0 sigHashAll (Just rdm) generatedSignedTx = signTx btc unsignedTx [sigIn] (take 2 keys) -- Reproduce the P2SH-P2WSH multisig example from BIP 143 testBip143p2shp2wpkhMulsig :: Assertion testBip143p2shp2wpkhMulsig = case getImpl of Just ImplCore -> assertEqual "BIP143 Core p2sh-p2wsh multisig" (Right signedTxCore) generatedSignedTx Just ImplABC -> assertEqual "BIP143 Core p2sh-p2wsh multisig" (Right signedTxABC) generatedSignedTx Nothing -> assertFailure "Invalid secp256k1 library" where signedTxCore = "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa10\ \6c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b8\ \9af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914\ \389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976\ \a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206a\ \c44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b02\ \20367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870\ \ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1\ \e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa26955\ \78cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e613\ \9ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9f\ \e7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d5\ \0b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf\ \88f91265f5a942e96213afae16d83321c8b31bb342142a14d163814830450221\ \00a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a4\ \07022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e\ \1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d\ \8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa8\ \4c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b33\ \34e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c\ \3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b97\ \81957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a\ \21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba0\ \4d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b330\ \2ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000" signedTxABC = "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa10\ \6c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b8\ \9af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914\ \389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976\ \a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac0800483045022100\ \b70b684ef0d17b51adf71c0dae932beca5d447dd5eec03394328436bdba836e7\ \0220208ebfd7408d21e41da11d8287655528385429d3fe300bee241f10944339\ \5b580147304402204b5f9bc06c8f0a252b9842ea44785853beb1638002cec5f2\ \489d73e5f6f5109302204f3b132b32638835d4b1a651e7d18dc93c10192db553\ \999932af6a8e3d8a153202483045022100e0ed8d3a245a138c751d74e1359aee\ \6a52476ddf33a3a9a5f0c2ad30147319650220581318187061ad0f48fc4f5c85\ \1822e554d59977005b8de4b78bf2ce2fe8399703483045022100a0a40abc581e\ \4b725775a3aa93bf0f0fd9a02ad3aa0f93483214784a47ba5387022069151c30\ \f85a7e20c8671107c5af884ee4c5a82bd06398327fa68a993f7cc64b81473044\ \022016d828460f6fab3cf89ae4b87c8f02c11c798cf739967f3b7406e7367c29\ \ae8b022079e82b822eb6c37a66efabc3f0b40a2b98c52f848d36463f6623cbdc\ \fe675812824730440220225a14ba7434858dbb5e6e0a0969ddf3b5455edaabf9\ \9f5773d1f59e7816b918022047ed1ab87840a74f7e9489f3af051e5fd26b790f\ \b308c79f4b0ed73c0422795d83cf56210307b8ae49ac90a048e9b53357a2354b\ \3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac\ \5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b\ \9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a\ \9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94b\ \a04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3\ \302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000" unsignedTx = "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1c\ \a29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd\ \9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a3\ \3f950689af511e6e84c138dbbd3c3ee41588ac00000000" op0 = head $ prevOutput <$> txIn unsignedTx rawKeys = [ "730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6" , "11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3" , "77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661" , "14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49" , "fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323" , "428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890" ] Just keys = traverse secHexKey rawKeys rdm = PayMulSig (toPubKey <$> keys) 6 sigIn sh = SigInput (toP2WSH $ encodeOutput rdm) 987654321 op0 sh (Just rdm) sigHashesA = [sigHashAll, sigHashNone, sigHashSingle] sigHashesB = setAnyoneCanPayFlag <$> sigHashesA sigIns = sigIn <$> (sigHashesA <> sigHashesB) generatedSignedTx = foldM addSig unsignedTx $ zip sigIns keys addSig tx (sigIn', key') = signNestedWitnessTx btc tx [sigIn'] [key'] secHexKey :: Text -> Maybe SecKey secHexKey = decodeHex >=> secKey toPubKey :: SecKey -> PubKeyI toPubKey = derivePubKeyI . wrapSecKey True