Skip to content

Commit

Permalink
Merge pull request #193 from input-output-hk/paweljakubas/adp-1985/en…
Browse files Browse the repository at this point in the history
…able-using-key-hashes

Enable using key hashes
  • Loading branch information
paweljakubas authored Aug 4, 2022
2 parents 4a36908 + d9ea3b8 commit 8f57479
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 15 deletions.
42 changes: 38 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,33 @@ addr_test1vp2fg770ddmqxxduasjsas39l5wwvwa04nj8ud95fde7f7guscp6v
</details>

<details>
<summary>How to generate a delegated payment address from a stake key (<strong>payment-delegated.addr</strong>)</summary>
<summary>How to generate a payment address from a payment key hash (<strong>payment.addr</strong>)</summary>

```console
$ cardano-address address delegation $(cat stake.xvk) < payment.addr > payment-delegated.addr
$ cardano-address key hash < addr.xvk > addr.vkh
addr_vkh12j28hnmtwcp3n08vy58vyf0arnnrhtavu3lrfdztw0j0jng3d6v
$ cardano-address address payment --network-tag testnet < addr.vkh > payment.addr
addr_test1vp2fg770ddmqxxduasjsas39l5wwvwa04nj8ud95fde7f7guscp6v
```
</details>


<details>
<summary>How to generate a delegated payment address, i.e. base address, from a stake key (<strong>base.addr</strong>)</summary>

```console
$ cardano-address address delegation $(cat stake.xvk) < payment.addr > base.addr
addr_test1qp2fg770ddmqxxduasjsas39l5wwvwa04nj8ud95fde7f70k6tew7wrnx0s4465nx05ajz890g44z0kx6a3gsnms4c4qq8ve0n
```
</details>

<details>
<summary>How to generate a delegated payment address, i.e. base address, from a stake key hash (<strong>base.addr</strong>)</summary>

```console
$ cardano-address key hash < stake.xvk > stake.vkh
stake_vkh17mf09mecwve7zkh2jve7nkggu4azk5f7cmtk9zz0wzhz5efq2w6
$ cardano-address address delegation $(cat stake.vkh) < payment.addr > base.addr
addr_test1qp2fg770ddmqxxduasjsas39l5wwvwa04nj8ud95fde7f70k6tew7wrnx0s4465nx05ajz890g44z0kx6a3gsnms4c4qq8ve0n
```
</details>
Expand All @@ -186,7 +209,18 @@ addr_test1qp2fg770ddmqxxduasjsas39l5wwvwa04nj8ud95fde7f70k6tew7wrnx0s4465nx05ajz

```console
$ cardano-address address stake --network-tag testnet < stake.xvk > stake.addr
stake_test1urmd9uh08pen8c26a2fn86weprjh52638mrdwc5gfac2u2s25zpat%
stake_test1urmd9uh08pen8c26a2fn86weprjh52638mrdwc5gfac2u2s25zpat
```
</details>

<details>
<summary>How to generate a stake address from a stake key hash (<strong>stake.addr</strong>)</summary>

```console
$ cardano-address key hash < stake.xvk > stake.vkh
stake_vkh17mf09mecwve7zkh2jve7nkggu4azk5f7cmtk9zz0wzhz5efq2w6
$ cardano-address address stake --network-tag testnet < stake.vkh > stake.addr
stake_test1urmd9uh08pen8c26a2fn86weprjh52638mrdwc5gfac2u2s25zpat
```
</details>

Expand Down Expand Up @@ -270,7 +304,7 @@ $ cardano-address script hash "all [$(cat addr_shared.1.vk), $(cat addr_shared.2
script1gr69m385thgvkrtspk73zmkwk537wxyxuevs2u9cukglvtlkz4k
```

This script requires the signature from both signing keys corresponding to `shared_addr.1.vk` and `shared_addr.2.vk` (ie., shared_addr.1.sk and shared_addr.2.sk) in order to be valid. Similarly, we could require only one of the two signatures:
This script requires the signature from both signing keys corresponding to `shared_addr.1.vk` and `shared_addr.2.vk` (i.e., shared_addr.1.sk and shared_addr.2.sk) in order to be valid. Similarly, we could require only one of the two signatures:

We can also use extended verification, eiher payment or delegation, keys. They can be obtained as the non-extended ones by using `--with-chain-code` option rather than `--without-chain-option` as above. They will give rise to the same script hash as for verification keys chain code is stripped upon calculation.

Expand Down
2 changes: 2 additions & 0 deletions command-line/lib/Command/Address/Delegation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ run Cmd{credential} = do
fail msg
Left (ErrInvalidAddressType msg) ->
fail msg
Left (ErrInvalidKeyHashType msg) ->
fail msg
Right addr ->
B8.hPutStr stdout $ T.encodeUtf8 $ bech32 addr
where
Expand Down
11 changes: 10 additions & 1 deletion command-line/lib/Command/Address/Payment.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Cardano.Address
import Cardano.Address.Derivation
( xpubFromBytes )
import Cardano.Address.Script
( scriptHashFromBytes )
( KeyRole (..), keyHashFromBytes, scriptHashFromBytes )
import Cardano.Address.Style.Shelley
( Credential (..), shelleyTestnet )
import Codec.Binary.Encoding
Expand Down Expand Up @@ -83,6 +83,7 @@ run Cmd{networkTag} = do
-- this stage, so leaving this as an item for later.
allowedPrefixes =
[ CIP5.addr_xvk
, CIP5.addr_vkh
, CIP5.script
]

Expand All @@ -95,6 +96,14 @@ run Cmd{networkTag} = do
let credential = PaymentFromScript h
pure $ Shelley.paymentAddress discriminant credential

| hrp == CIP5.addr_vkh = do
case keyHashFromBytes (Payment, bytes) of
Nothing ->
fail "Couldn't convert bytes into payment key hash."
Just keyhash -> do
let credential = PaymentFromKeyHash keyhash
pure $ Shelley.paymentAddress discriminant credential

| otherwise = do
case xpubFromBytes bytes of
Nothing ->
Expand Down
4 changes: 3 additions & 1 deletion command-line/lib/Command/Address/Pointer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ run Cmd{slotNum,transactionIndex,outputIndex} = do
case Shelley.extendAddress (unsafeMkAddress bytes) (DelegationFromPointer ptr) of
Left (ErrInvalidAddressStyle msg) ->
fail msg
Left (ErrInvalidAddressType msg) ->
Left (ErrInvalidAddressType msg) ->
fail msg
Left (ErrInvalidKeyHashType msg) ->
fail msg
Right addr ->
B8.hPutStr stdout $ T.encodeUtf8 $ bech32 addr
Expand Down
11 changes: 10 additions & 1 deletion command-line/lib/Command/Address/Reward.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Cardano.Address
import Cardano.Address.Derivation
( xpubFromBytes )
import Cardano.Address.Script
( scriptHashFromBytes )
( KeyRole (..), keyHashFromBytes, scriptHashFromBytes )
import Cardano.Address.Style.Shelley
( Credential (..), shelleyTestnet, unsafeFromRight )
import Codec.Binary.Encoding
Expand Down Expand Up @@ -84,6 +84,7 @@ run Cmd{networkTag} = do
-- this stage, so leaving this as an item for later.
allowedPrefixes =
[ CIP5.stake_xvk
, CIP5.stake_vkh
, CIP5.script
]

Expand All @@ -96,6 +97,14 @@ run Cmd{networkTag} = do
let credential = DelegationFromScript h
pure $ unsafeFromRight $ Shelley.stakeAddress discriminant credential

| hrp == CIP5.stake_vkh = do
case keyHashFromBytes (Delegation, bytes) of
Nothing ->
fail "Couldn't convert bytes into delegation key hash."
Just keyhash -> do
let credential = DelegationFromKeyHash keyhash
pure $ unsafeFromRight $ Shelley.stakeAddress discriminant credential

| otherwise = do
case xpubFromBytes bytes of
Nothing ->
Expand Down
17 changes: 12 additions & 5 deletions command-line/lib/Options/Applicative/Credential.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import Cardano.Address.Derivation
( Depth (..) )
import Cardano.Address.Internal
( orElse )
import Cardano.Address.Script
( KeyRole (..) )
import Cardano.Address.Style.Shelley
( Credential (..), liftXPub )
import Options.Applicative
( Parser, argument, eitherReader, help, metavar )
import Options.Applicative.Derivation
( xpubReader )
( keyhashReader, xpubReader )
import Options.Applicative.Script
( scriptHashReader )

Expand All @@ -30,18 +32,23 @@ import qualified Cardano.Codec.Bech32.Prefixes as CIP5

delegationCredentialArg :: String -> Parser (Credential 'DelegationK)
delegationCredentialArg helpDoc = argument (eitherReader reader) $ mempty
<> metavar "KEY || SCRIPT HASH"
<> metavar "KEY || KEY HASH || SCRIPT HASH"
<> help helpDoc
where
reader :: String -> Either String (Credential 'DelegationK)
reader str =
(DelegationFromKey . liftXPub <$> xpubReader allowedPrefixes str)
(DelegationFromKey . liftXPub <$> xpubReader allowedPrefixesForXPub str)
`orElse`
(DelegationFromKeyHash <$> keyhashReader (Delegation, allowedPrefixesForKeyHash) str)
`orElse`
(DelegationFromScript <$> scriptHashReader str)
`orElse`
Left "Couldn't parse delegation credentials. Neither a public key nor a script hash."
Left "Couldn't parse delegation credentials. Neither a public key, a public key hash nor a script hash."

-- TODO: Allow non-extended keys here.
allowedPrefixes =
allowedPrefixesForXPub =
[ CIP5.stake_xvk
]
allowedPrefixesForKeyHash =
[ CIP5.stake_vkh
]
13 changes: 12 additions & 1 deletion command-line/lib/Options/Applicative/Derivation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ module Options.Applicative.Derivation
, derivationIndexToString
, derivationIndexFromString

-- * XPub / XPrv
-- * XPub / XPrv / KeyHash
, xpubReader
, xpubOpt
, xpubArg
, keyhashReader

-- * Internal
, bech32Reader
Expand All @@ -39,6 +40,8 @@ import Prelude

import Cardano.Address.Derivation
( DerivationType (..), Index, XPub, wholeDomainIndex, xpubFromBytes )
import Cardano.Address.Script
( KeyHash (..), KeyRole (..), keyHashFromBytes )
import Codec.Binary.Bech32
( HumanReadablePart, humanReadablePartToText )
import Codec.Binary.Encoding
Expand Down Expand Up @@ -210,6 +213,14 @@ xpubArg allowedPrefixes helpDoc =
<> metavar "XPUB"
<> help helpDoc

keyhashReader :: (KeyRole, [HumanReadablePart]) -> String -> Either String KeyHash
keyhashReader (keyrole, allowedPrefixes) str = do
(_hrp, bytes) <- bech32Reader allowedPrefixes str
case keyHashFromBytes (keyrole, bytes) of
Just keyhash -> pure keyhash
Nothing -> Left
"Failed to convert bytes into a valid public key hash."

--
-- Internal
--
Expand Down
19 changes: 19 additions & 0 deletions command-line/test/Command/Address/DelegationSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ spec = describeCmd [ "address", "delegation" ] $ do
"addr_test1qptherz8fgux9ywdysrcpaclznyyvl23l2zfcery3f4m9qwv\
\xwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qwk2gqr"

specFromKeyHash defaultPhrase "1852H/1815H/0H/2/0"
defaultAddrMainnet
"addr1q9therz8fgux9ywdysrcpaclznyyvl23l2zfcery3f4m9qwvxwdrt\
\70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qdqhgvu"

specFromKeyHash defaultPhrase "1852H/1815H/0H/2/0"
defaultAddrTestnet
"addr_test1qptherz8fgux9ywdysrcpaclznyyvl23l2zfcery3f4m9qwv\
\xwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qwk2gqr"

specFromScript
defaultAddrMainnet
"all [stake_shared_vkh1nqc00hvlc6cq0sfhretk0rmzw8dywmusp8retuqnnxzajtzhjg5]"
Expand Down Expand Up @@ -56,6 +66,15 @@ specFromKey phrase path addr want = it ("delegation from key " <> want) $ do
out <- cli [ "address", "delegation", stakeKey ] addr
out `shouldBe` want

specFromKeyHash :: [String] -> String -> String -> String -> SpecWith ()
specFromKeyHash phrase path addr want = it ("delegation from key " <> want) $ do
stakeKeyHash <- cli [ "key", "from-recovery-phrase", "shelley" ] (unwords phrase)
>>= cli [ "key", "child", path ]
>>= cli [ "key", "public", "--with-chain-code" ]
>>= cli [ "key", "hash" ]
out <- cli [ "address", "delegation", stakeKeyHash ] addr
out `shouldBe` want

specFromScript :: String -> String -> String -> SpecWith ()
specFromScript addr script want = it ("delegation from script " <> want) $ do
scriptHash <- cli [ "script", "hash", script ] ""
Expand Down
21 changes: 21 additions & 0 deletions command-line/test/Command/Address/PaymentSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ spec = describeCmd [ "address", "payment" ] $ do
specShelley defaultPhrase "1852H/1815H/0H/0/0" "mainnet"
"addr1v9u5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg0kvk0f"

specShelleyFromKeyHash defaultPhrase "1852H/1815H/0H/0/0" "0"
"addr_test1vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg57c2qv"

specShelleyFromKeyHash defaultPhrase "1852H/1815H/0H/0/0" "3"
"addr1vdu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg0m9a08"

specShelleyFromKeyHash defaultPhrase "1852H/1815H/0H/0/0" "testnet"
"addr_test1vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg57c2qv"

specShelleyFromKeyHash defaultPhrase "1852H/1815H/0H/0/0" "mainnet"
"addr1v9u5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg0kvk0f"

specMalformedNetwork "💩"

specInvalidNetwork "42"
Expand All @@ -38,6 +50,15 @@ specShelley phrase path networkTag want = it ("golden shelley (payment) " <> pat
>>= cli [ "address", "payment", "--network-tag", networkTag ]
out `shouldBe` want

specShelleyFromKeyHash :: [String] -> String -> String -> String -> SpecWith ()
specShelleyFromKeyHash phrase path networkTag want = it ("golden shelley (payment) " <> path) $ do
out <- cli [ "key", "from-recovery-phrase", "shelley" ] (unwords phrase)
>>= cli [ "key", "child", path ]
>>= cli [ "key", "public", "--with-chain-code" ]
>>= cli [ "key", "hash" ]
>>= cli [ "address", "payment", "--network-tag", networkTag ]
out `shouldBe` want

specMalformedNetwork :: String -> SpecWith ()
specMalformedNetwork networkTag = it ("malformed network " <> networkTag) $ do
(out, err) <- cli [ "key", "from-recovery-phrase", "shelley" ] (unwords defaultPhrase)
Expand Down
15 changes: 15 additions & 0 deletions command-line/test/Command/Address/RewardSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ spec = describeCmd [ "address", "stake" ] $ do
specShelley defaultPhrase "1852H/1815H/0H/2/0" 3
"stake1u0a3dk68y6echdmfmnvm8mej8u5truwv8ufmv830w5a45tchw5z0e"

specShelleyFromKeyHash defaultPhrase "1852H/1815H/0H/2/0" 0
"stake_test1ura3dk68y6echdmfmnvm8mej8u5truwv8ufmv830w5a45tcsfhtt2"

specShelleyFromKeyHash defaultPhrase "1852H/1815H/0H/2/0" 3
"stake1u0a3dk68y6echdmfmnvm8mej8u5truwv8ufmv830w5a45tchw5z0e"

specMalformedNetwork "💩"

specInvalidNetwork "42"
Expand All @@ -31,6 +37,15 @@ specShelley phrase path networkTag want = it ("golden shelley (payment) " <> pat
>>= cli [ "address", "stake", "--network-tag", show networkTag ]
out `shouldBe` want

specShelleyFromKeyHash :: [String] -> String -> Int -> String -> SpecWith ()
specShelleyFromKeyHash phrase path networkTag want = it ("golden shelley (payment) " <> path) $ do
out <- cli [ "key", "from-recovery-phrase", "shelley" ] (unwords phrase)
>>= cli [ "key", "child", path ]
>>= cli [ "key", "public", "--with-chain-code" ]
>>= cli [ "key", "hash" ]
>>= cli [ "address", "stake", "--network-tag", show networkTag ]
out `shouldBe` want

specMalformedNetwork :: String -> SpecWith ()
specMalformedNetwork networkTag = it ("malformed network " <> networkTag) $ do
(out, err) <- cli [ "key", "from-recovery-phrase", "shelley" ] (unwords defaultPhrase)
Expand Down
12 changes: 12 additions & 0 deletions core/lib/Cardano/Address/Script.hs
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,17 @@ keyHashToText (KeyHash cred keyHash) = case cred of
-- Bech32 encoded text with one of following hrp:
-- - `addr_shared_vkh`
-- - `stake_shared_vkh`
-- - `addr_vkh`
-- - `stake_vkh`
-- - `policy_vkh`
-- - `addr_shared_vk`
-- - `stake_shared_vk`
-- - `addr_vk`
-- - `stake_vk`
-- - `addr_shared_xvk`
-- - `stake_shared_xvk`
-- - `addr_xvk`
-- - `stake_xvk`
-- - `policy_vk`
-- - `policy_xvk`
-- Raw keys will be hashed on the fly, whereas hash that are directly
Expand All @@ -282,11 +288,17 @@ keyHashFromText txt = do
convertBytes hrp bytes
| hrp == CIP5.addr_shared_vkh = Just (Payment, bytes)
| hrp == CIP5.stake_shared_vkh = Just (Delegation, bytes)
| hrp == CIP5.addr_vkh = Just (Payment, bytes)
| hrp == CIP5.stake_vkh = Just (Delegation, bytes)
| hrp == CIP5.policy_vkh = Just (Policy, bytes)
| hrp == CIP5.addr_shared_vk = Just (Payment, hashCredential bytes)
| hrp == CIP5.addr_vk = Just (Payment, hashCredential bytes)
| hrp == CIP5.addr_shared_xvk = Just (Payment, hashCredential $ BS.take 32 bytes)
| hrp == CIP5.addr_xvk = Just (Payment, hashCredential $ BS.take 32 bytes)
| hrp == CIP5.stake_shared_vk = Just (Delegation, hashCredential bytes)
| hrp == CIP5.stake_vk = Just (Delegation, hashCredential bytes)
| hrp == CIP5.stake_shared_xvk = Just (Delegation, hashCredential $ BS.take 32 bytes)
| hrp == CIP5.stake_xvk = Just (Delegation, hashCredential $ BS.take 32 bytes)
| hrp == CIP5.policy_vk = Just (Policy, hashCredential bytes)
| hrp == CIP5.policy_xvk = Just (Policy, hashCredential $ BS.take 32 bytes)
| otherwise = Nothing
Expand Down
Loading

0 comments on commit 8f57479

Please sign in to comment.