haskoin-core-0.15.0: test/Haskoin/Crypto/SignatureSpec.hs
{-# 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