bitcoin-scripting-0.1.0: test/Test/Miniscript/Witness.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
module Test.Miniscript.Witness (
witnessTests,
) where
import Data.ByteString (ByteString)
import Data.Serialize (encode)
import Data.Text (Text)
import Haskoin.Crypto (
ripemd160,
sha256,
signHash,
)
import Haskoin.Keys (PubKeyI, secKeyData)
import Haskoin.Script (
Script (..),
ScriptOp (..),
TxSignature (..),
encodeTxSig,
opPushData,
sigHashAll,
)
import Haskoin.Util.Arbitrary.Keys (arbitraryKeyPair)
import Haskoin.Util.Arbitrary.Util (arbitraryBSn)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (
Gen,
Property,
Testable,
forAll,
(===),
)
import Language.Bitcoin.Miniscript (Miniscript (..), let_)
import Language.Bitcoin.Miniscript.Witness (
ChainState (..),
SatisfactionError (..),
Signature (..),
emptyChainState,
preimage,
satisfactionContext,
satisfy,
signature,
)
import Language.Bitcoin.Script.Descriptors (pubKey)
import Test.Example (
Example (..),
testExampleProperty,
)
import qualified Test.Miniscript.Examples as E
import Test.Utils (forAllLabeled, pr23)
witnessTests :: TestTree
witnessTests = testGroup "witness" examples
where
examples =
[ example1
, example2
, example3
, example4
, example5
, example6
, example7
, example8
, example9
, example10
]
pushKey :: PubKeyI -> ScriptOp
pushKey = opPushData . encode
pushSig :: Signature -> ScriptOp
pushSig (Signature s sh) = opPushData . encodeTxSig $ TxSignature s sh
forKeys :: Testable p => [Text] -> Miniscript -> ([(PubKeyI, Signature)] -> Miniscript -> p) -> Property
forKeys ls scr k = forAllLabeled arbKeySig mkRow ls mkProp
where
mkRow label (pk, s) = (label, pk, s)
mkProp xs = k (pr23 <$> xs) $ let_ (binding <$> xs) scr
binding (l, pk, _) = (l, KeyDesc $ pubKey pk)
arbKeySig :: Gen (PubKeyI, Signature)
arbKeySig = repack <$> arbitraryKeyPair
where
repack (sk, pk) = (pk, mkSig $ secKeyData sk)
mkSig s = Signature (signHash s $ sha256 msg) sigHashAll
msg :: ByteString
msg = "arbKeySig"
testExample ::
Testable p =>
Example Miniscript ->
[Text] ->
([(PubKeyI, Signature)] -> Miniscript -> p) ->
TestTree
testExample e ls = testExampleProperty e . forKeys ls (script e)
example1 :: TestTree
example1 = testExample E.example1 ["key_1"] test
where
test [(k, s)] scr = satisfy emptyChainState (signature k s) scr === Right (Script [pushSig s])
example2 :: TestTree
example2 = testExample E.example2 ["key_1", "key_2"] test
where
test xs scr = satisfy emptyChainState (context xs) scr === Right (expected xs)
expected ((_, s) : _) = Script [OP_0, pushSig s]
context (x : _) = uncurry signature x
example3 :: TestTree
example3 = testExample E.example3 ["key_likely", "key_unlikely"] test
where
test xs scr = satisfy emptyChainState (context xs) scr === Right (expected xs)
expected (_ : (k, s) : _) = Script [pushSig s, pushKey k, OP_0]
context (_ : x : _) = uncurry signature x
example4 :: TestTree
example4 = testExample E.example4 ["key_user", "key_service"] test
where
test xs scr = satisfy chainState (context xs) scr === Right (expected xs)
expected ((_, s) : _) = Script [OP_0, pushSig s]
context (x : _) = uncurry signature x
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 20000}
example5 :: TestTree
example5 = testExample E.example5 ["key_1", "key_2", "key_3"] test
where
test xs scr = result xs scr === Right (expected xs)
expected [(_, s1), _, (_, s3)] = Script [OP_1, pushSig s3, OP_0, pushSig s1]
result [x1, _, x3] scr = satisfy chainState (context x1 x3) scr
context (k1, s1) (k3, s3) = signature k1 s1 <> signature k3 s3
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 13000}
example6 :: TestTree
example6 = testExample E.example6 ["key_local", "key_revocation"] test
where
test xs scr = result xs scr === Right (expected xs)
expected [_, (_, s2)] = Script [pushSig s2, OP_0]
result xs scr = satisfy chainState (context xs) scr
context = foldMap (uncurry signature)
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 100}
hashBinding :: ByteString -> Miniscript -> Miniscript
hashBinding bs = let_ [("H", Bytes . encode $ ripemd160 bs)]
example7 :: TestTree
example7 = testExample E.example7 ["key_local", "key_remote", "key_revocation"] test
where
test xs scr = forAll (arbitraryBSn 32) $ \bs ->
satisfy chainState (context xs bs) (hashBinding bs scr) === Right (expected xs bs)
expected [_, (_, s), _] bs = Script [opPushData bs, OP_0, pushSig s, OP_0]
context [_, x, _] bs = uncurry signature x <> preimage (encode $ ripemd160 bs) bs
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 2000}
example8 :: TestTree
example8 = testExample E.example8 ["key_local", "key_remote", "key_revocation"] test
where
test xs scr = forAll (arbitraryBSn 32) $ \bs ->
satisfy chainState (context xs bs) (hashBinding bs scr) === Right (expected xs bs)
expected [(kl, sl), (_, sr), _] bs = Script [opPushData bs, pushSig sl, pushKey kl, OP_1, pushSig sr]
context [l, r, _] bs = satisfactionContext [(encode $ ripemd160 bs, bs)] [l, r]
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 6}
example9 :: TestTree
example9 = testExample E.example9 [] test
where
test _ scr = satisfy chainState mempty scr === Left Impossible
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 100}
example10 :: TestTree
example10 = testExample E.example10 ["A", "B", "C", "D", "E", "F", "G", "H"] test
where
test xs scr = satisfy chainState (context xs) scr === Right (expected xs)
expected xs = Script $ OP_0 : drop 1 (foldMap pushPkhSig (reverse $ drop 5 xs)) <> replicate 5 OP_0
context xs = foldMap (uncurry signature) . take 2 $ drop 5 xs
chainState = ChainState{blockHeight = Nothing, utxoAge = Just 20000}
pushPkhSig (k, s) = [pushSig s, pushKey k]