Skip to content

Commit

Permalink
add property for isShared - part 1
Browse files Browse the repository at this point in the history
some debugs

more debugs

fix how account public xpub is generated
  • Loading branch information
paweljakubas committed Nov 19, 2020
1 parent baab28d commit d7cfc3d
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 14 deletions.
1 change: 1 addition & 0 deletions lib/core/cardano-wallet-core.cabal
Expand Up @@ -311,6 +311,7 @@ test-suite unit
Cardano.Wallet.Primitive.CoinSelectionSpec
Cardano.Wallet.Primitive.FeeSpec
Cardano.Wallet.Primitive.ModelSpec
Cardano.Wallet.Primitive.ScriptsSpec
Cardano.Wallet.Primitive.SlottingSpec
Cardano.Wallet.Primitive.SyncProgressSpec
Cardano.Wallet.Primitive.TypesSpec
Expand Down
36 changes: 22 additions & 14 deletions lib/core/src/Cardano/Wallet/Primitive/Scripts.hs
Expand Up @@ -5,7 +5,6 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
Expand All @@ -22,6 +21,8 @@

module Cardano.Wallet.Primitive.Scripts
( isShared
, retrieveAllVerKeyHashes
, toKeyHash
) where

import Prelude
Expand Down Expand Up @@ -60,26 +61,33 @@ isShared
-> SeqState n k
-> ([k 'ScriptK XPub], SeqState n k)
isShared script s@(SeqState !s1 !s2 !ixs !rpk !prefix !scripts) =
let verKeysInScript = extractVerKey [] script
toVerKey = deriveAddressPublicKey (accountPubKey s2) MultisigScript
let verKeysInScript = retrieveAllVerKeyHashes script
accXPub = accountPubKey s2
toVerKey ix = deriveAddressPublicKey accXPub MultisigScript ix
minIndex = getIndex @'Soft minBound
scriptAddressGap = 10
toKeyHash = KeyHash . blake2b224 . xpubPublicKey . getKey
ourVerKeyHashes =
ourVerKeys =
map (\ix -> toVerKey (toEnum (fromInteger $ toInteger $ minIndex + ix)))
[0 .. scriptAddressGap - 1]
ourVerKeysInScript =
[0 .. scriptAddressGap]
ourVerKeyHashesInScript =
filter (\keyH -> toKeyHash keyH `elem` verKeysInScript)
ourVerKeyHashes
ourVerKeys
toScriptXPub (ShelleyKey k) = ShelleyKey k
scriptXPubs = map toScriptXPub ourVerKeysInScript
in if null ourVerKeysInScript then
scriptXPubs = map toScriptXPub ourVerKeyHashesInScript
in if null ourVerKeyHashesInScript then
([], s)
else
( scriptXPubs
, SeqState s1 s2 ixs rpk prefix (Map.insert (toScriptHash script) scriptXPubs scripts))
where
extractVerKey acc (RequireSignatureOf verKey) = verKey : acc
extractVerKey acc (RequireAllOf xs) = foldr (flip extractVerKey) acc xs
extractVerKey acc (RequireAnyOf xs) = foldr (flip extractVerKey) acc xs
extractVerKey acc (RequireSomeOf _ xs) = foldr (flip extractVerKey) acc xs

retrieveAllVerKeyHashes :: Script -> [KeyHash]
retrieveAllVerKeyHashes = extractVerKey []
where
extractVerKey acc (RequireSignatureOf verKey) = verKey : acc
extractVerKey acc (RequireAllOf xs) = foldr (flip extractVerKey) acc xs
extractVerKey acc (RequireAnyOf xs) = foldr (flip extractVerKey) acc xs
extractVerKey acc (RequireSomeOf _ xs) = foldr (flip extractVerKey) acc xs

toKeyHash :: ShelleyKey depth XPub -> KeyHash
toKeyHash = KeyHash . blake2b224 . xpubPublicKey . getKey
1 change: 1 addition & 0 deletions lib/core/test/bench/db/Main.hs
Expand Up @@ -327,6 +327,7 @@ bgroupWriteSeqState db = bgroup "SeqState"
emptyPendingIxs
rewardAccount
defaultPrefix
Map.empty
| i <- [1..n]
]

Expand Down
184 changes: 184 additions & 0 deletions lib/core/test/unit/Cardano/Wallet/Primitive/ScriptsSpec.hs
@@ -0,0 +1,184 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Cardano.Wallet.Primitive.ScriptsSpec
( spec
) where

import Prelude

import Cardano.Address.Derivation
( XPub )
import Cardano.Address.Script
( KeyHash (..), Script (..) )
import Cardano.Mnemonic
( SomeMnemonic (..) )
import Cardano.Wallet.Gen
( genMnemonic )
import Cardano.Wallet.Primitive.AddressDerivation
( Depth (..)
, DerivationType (..)
, HardDerivation (..)
, Index (..)
, Passphrase (..)
, PassphraseMaxLength (..)
, PassphraseMinLength (..)
, Role (..)
, SoftDerivation (..)
, WalletKey (..)
, hex
, preparePassphrase
)
import Cardano.Wallet.Primitive.AddressDerivation.Shelley
( ShelleyKey (..), generateKeyFromSeed )
import Cardano.Wallet.Primitive.AddressDiscovery.Sequential
( DerivationPrefix (..)
, SeqState (..)
, coinTypeAda
, defaultAddressPoolGap
, emptyPendingIxs
, mkAddressPool
, purposeCIP1852
, purposeCIP1852
)
import Cardano.Wallet.Primitive.Scripts
( isShared, retrieveAllVerKeyHashes, toKeyHash )
import Cardano.Wallet.Primitive.Types
( PassphraseScheme (..) )
import Cardano.Wallet.Unsafe
( unsafeXPub )
import Control.Monad
( replicateM )
import Data.Proxy
( Proxy (..) )
import Test.Hspec
( Spec, describe, it )
import Test.QuickCheck
( Arbitrary (..)
, Gen
, Positive (..)
, Property
, arbitraryPrintableChar
, choose
, elements
, property
, scale
, sized
, vectorOf
, (===)
)

import qualified Data.ByteArray as BA
import qualified Data.ByteString.Char8 as B8
import qualified Data.List as L
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as T

spec :: Spec
spec = do
describe "isShared" $ do
it "script composed with all of our verification keys should discover them all"
(property prop_scriptFromOurVerKeys)

prop_scriptFromOurVerKeys
:: AccountXPubWithScript
-> Property
prop_scriptFromOurVerKeys (AccountXPubWithScript accXPub' script') = do
let sciptHashes = retrieveAllVerKeyHashes script'
let intPool = mkAddressPool accXPub' defaultAddressPoolGap []
let extPool = mkAddressPool accXPub' defaultAddressPoolGap []
let seqState =
SeqState intPool extPool emptyPendingIxs dummyRewardAccount defaultPrefix Map.empty
let (ourSharedKeys, _) = isShared script' seqState
L.sort (L.nub $ map toKeyHash ourSharedKeys) === L.sort (L.nub sciptHashes)

data AccountXPubWithScript = AccountXPubWithScript
{ accXPub :: ShelleyKey 'AccountK XPub
, script :: Script
} deriving (Eq, Show)

defaultPrefix :: DerivationPrefix
defaultPrefix = DerivationPrefix
( purposeCIP1852
, coinTypeAda
, minBound
)

dummyRewardAccount :: ShelleyKey 'AddressK XPub
dummyRewardAccount = ShelleyKey $ unsafeXPub $ B8.replicate 64 '0'

instance Ord KeyHash where
compare (KeyHash kh1) (KeyHash kh2) =
compare (T.decodeUtf8 $ hex kh1) (T.decodeUtf8 $ hex kh2)

{-------------------------------------------------------------------------------
Arbitrary Instances
-------------------------------------------------------------------------------}

genScript :: [KeyHash] -> Gen Script
genScript keyHashes =
scale (`div` 3) $ sized scriptTree
where
scriptTree 0 = do
keyH <- elements keyHashes
pure $ RequireSignatureOf keyH
scriptTree n = do
Positive m <- arbitrary
let n' = n `div` (m + 1)
scripts <- vectorOf m (scriptTree n')
atLeast <- choose (1, fromIntegral (m + 1))
elements
[ RequireAllOf scripts
, RequireAnyOf scripts
, RequireSomeOf atLeast scripts
]

instance Arbitrary AccountXPubWithScript where
arbitrary = do
mnemonics <- SomeMnemonic <$> genMnemonic @12
encPwd <- arbitrary
let rootXPrv = generateKeyFromSeed (mnemonics, Nothing) encPwd
let accXPub' = publicKey $ deriveAccountPrivateKey encPwd rootXPrv minBound
keyNum <- choose (2,8)
let toVerKey ix = deriveAddressPublicKey accXPub' MultisigScript ix
let minIndex = getIndex @'Soft minBound
let verKeys =
map (\ix -> toVerKey (toEnum (fromInteger $ toInteger $ minIndex + ix)))
[0 .. keyNum]
let verKeyHashes = map toKeyHash verKeys
AccountXPubWithScript accXPub' <$> genScript verKeyHashes

instance Arbitrary (Passphrase "raw") where
arbitrary = do
n <- choose (passphraseMinLength p, passphraseMaxLength p)
bytes <- T.encodeUtf8 . T.pack <$> replicateM n arbitraryPrintableChar
return $ Passphrase $ BA.convert bytes
where p = Proxy :: Proxy "raw"

shrink (Passphrase bytes)
| BA.length bytes <= passphraseMinLength p = []
| otherwise =
[ Passphrase
$ BA.convert
$ B8.take (passphraseMinLength p)
$ BA.convert bytes
]
where p = Proxy :: Proxy "raw"

instance Arbitrary (Passphrase "encryption") where
arbitrary = preparePassphrase EncryptWithPBKDF2
<$> arbitrary @(Passphrase "raw")

0 comments on commit d7cfc3d

Please sign in to comment.