cardano-api documentation

examples for using the cardano-api library


Project maintained by pb99u069 Hosted on GitHub Pages — Theme by mattgraham

The different keys

Cardano uses the the public-key signature system Ed25519 for a variety of private/public key pairs (resp. SigningKey/VerificationKeys in cardano-api terms). High level information about the different keys can be found here, while the interface to generate and serialise them is defined in the Key class in Cardano.Api.Keys.Class. The class instances for the different keys (called keyroles in the cardano-api) are in the following modules:

So, having a keyrole that is an instance of the Key class, we can generate the signing key using the following function:

generateSigningKey :: Key keyrole => AsType keyrole -> IO (SigningKey keyrole)
generateSigningKey keytype = do
    seed <- Crypto.readSeedFromSystemEntropy seedSize
    return $! deterministicSigningKey keytype seed
  where
    seedSize = deterministicSigningKeySeedSize keytype

where the deterministicSigningKey and deterministicSigningKeySeedSize functions are coming from the Key interface.

Maybe the most prominent keyrole on Cardano is the PaymentKey. Its SigningKey PaymentKey is used to sign transactions that spend funds locked at a key address (i.e an address whose payment credential is a hashed VerificationKey PaymentKey as opposed to a script address whose payment credential is a hashed script).

So, let’s take the keytype AsPaymentKey to generate a SigningKey PaymentKey. To achieve this, the function above uses the deterministicSigningKey function:

deterministicSigningKey :: AsType PaymentKey -> Crypto.Seed -> SigningKey PaymentKey
deterministicSigningKey AsPaymentKey seed =
    PaymentSigningKey (Crypto.genKeyDSIGN seed)

where the genKeyDSIGN function is coming from the DSIGNAlgorithm class, of which Ed25519DSIGN is an instance, see here.

The information that we need the Ed25519DSIGN instance of the DSIGNAlgorithm class is contained in the SigningKey PaymentKey which is defined as:

newtype SigningKey PaymentKey =
    PaymentSigningKey (Shelley.SignKeyDSIGN StandardCrypto)

with the cardano-ledger type SignKeyDSIGN StandardCrypto having the following type alias for the cardano-crypto-class:

type SignKeyDSIGN c = DSIGN.SignKeyDSIGN (DSIGN c)

Finally, the distinction between the cryptographic systems on the cardano-ledger side is made in the crypto class, respectively its instance for StandardCrypto here

instance Crypto StandardCrypto where
  type DSIGN StandardCrypto = Ed25519DSIGN
  type KES StandardCrypto = Sum6KES Ed25519DSIGN Blake2b_256
  type VRF StandardCrypto = PraosVRF
  type HASH StandardCrypto = Blake2b_256
  type ADDRHASH StandardCrypto = Blake2b_224

Normal and extended Shelley keys

Ten out of 14 keyroles are defined in Cardano.Api.Keys.Shelley (see above). And though they have different names, under the hood they fall in just two categories: normal shelley keys and extended shelley keys.

For the signing keys of these keyroles, we even have a unifying cardano-api type:

data ShelleySigningKey =
       -- | A normal ed25519 signing key
       ShelleyNormalSigningKey   (Shelley.SignKeyDSIGN StandardCrypto)
       -- | An extended ed25519 signing key
     | ShelleyExtendedSigningKey Crypto.HD.XPrv

A normal Shelley signing key is just what its name implies: A payment key that can be used to sign transactions. Most wallets for Cardano though are HD (hierarchical deterministic) wallets, also called multi-address wallets, and use an extended payment key, also called the root key, from which other (private and public) keys can be derived. Cardano HD wallets are similar to those described in BIP-0032, with the difference that they use (as mentioned above) Ed25519 arithmetic instead of Bitcoins P256K1 arithmethic.

The module to work with these extended keys is Cardano.Crypto.Wallet in the cardano-crypto package. The module contains, for example, the toXPub function which the cardano-api uses to convert a SigningKey PaymentExtendedKey into a VerificationKey PaymentExtendedKey (see getVerificationKey in the PaymentExtendedKey instance of the Key class). The module also contains the functions to derive child keys from signing and verification keys, these functions are not used by the cardano-api, though, but for example by the cardano-wallet here

The naming of the extended keys in the Cardano ecosystem can lead to some confusion. In places like here, we distinguish between Ed25519Bip32 (extended key with chaincode for derivation) and Ed25519Extended (extended key without chaincode). In the cardano-api, the latter is not used and a PaymentExtendedKey corresponds to the Ed25519Bip32 key. As the function below shows, the text envelope contains this bip32 information:

instance HasTextEnvelope (SigningKey PaymentExtendedKey) where
    textEnvelopeType _ = "PaymentExtendedSigningKeyShelley_ed25519_bip32"

Another source for confusion can be the serialisation of extended keys, see BIP16. the cardano-addresses tool for example encodes extended signing keys without the public key, as it can easily be derived from the public key, and that’s exactly what the following cardano-api function does:

xPrvFromBytes :: ByteString -> Maybe CC.XPrv
xPrvFromBytes bytes
    | BS.length bytes /= 96 = Nothing
    | otherwise = do
        let (prv, cc) = BS.splitAt 64 bytes
        pub <- ed25519ScalarMult (BS.take 32 prv)
        eitherToMaybe $ CC.xprv $ prv <> pub <> cc
  where
    eitherToMaybe :: Either a b -> Maybe b
    eitherToMaybe = either (const Nothing) Just

    ed25519ScalarMult :: ByteString -> Maybe ByteString
    ed25519ScalarMult bs = do
      scalar <- eitherToMaybe . eitherCryptoError $ Ed25519.scalarDecodeLong bs
      pure $ Ed25519.pointEncode $ Ed25519.toPoint scalar

This function shows nicely how the pub key is calculated and then added in between the extended private key and the chaincode. This function is for example used by the cardano-cli after having read a file containing a Bech32-encoded Ed25519 BIP32 extended signing key.

-- | Convert a Ed25519 BIP32 extended signing key (96 bytes) to a @cardano-crypto@
-- style extended signing key.
--
-- Note that both the ITN and @cardano-address@ use this key format.
convertBip32SigningKey
  :: ByteString
  -> Either CardanoAddressSigningKeyConversionError Crypto.XPrv
convertBip32SigningKey signingKeyBs =
  case xPrvFromBytes signingKeyBs of
    Just xPrv -> Right xPrv
    Nothing ->
      Left $ CardanoAddressSigningKeyDeserialisationError signingKeyBs

// If we want to derive child keys like here, we need a PaymentExtendedKey // see how cardano-wallet does this here // it uses generateNew and XPrv from cardano-crypto

// For the difference between normal and extended keys see here: // chain-wallet from iog // normal/extended keys on stack // good explanation to extended keys here

Interchangeable Shelley keys

Treating all shelley keys uniformely makes sense, for example during the transaction signing process. The makeShelleySignature function doesn’t need to know what keyrole the signing key has, as the process of signing data works identically for all shelley keys. The only case distinction the function makes is between normal keys and extended keys:

makeShelleySignature
  :: Crypto.SignableRepresentation tosign
  => tosign
  -> ShelleySigningKey
  -> Shelley.SignedDSIGN StandardCrypto tosign
makeShelleySignature tosign (ShelleyNormalSigningKey sk) =
    Crypto.signedDSIGN () tosign sk

makeShelleySignature tosign (ShelleyExtendedSigningKey sk) =
    fromXSignature $
      Crypto.HD.sign
        BS.empty  -- passphrase for (unused) in-memory encryption
        sk
        (Crypto.getSignableRepresentation tosign)
  where
    fromXSignature :: Crypto.HD.XSignature
                   -> Shelley.SignedDSIGN StandardCrypto b
    fromXSignature =
        Crypto.SignedDSIGN
      . fromMaybe impossible
      . Crypto.rawDeserialiseSigDSIGN
      . Crypto.HD.unXSignature

    impossible =
      error "makeShelleyKeyWitnessSignature: byron and shelley signature sizes do not match"

The cardano-api calls makeShelleySignature inside the makeShelleyKeyWittness function. To get from the (keyrole sensitive) ShelleyWitnessSigningKey to the ShelleySigningKey, it uses toShelleySigningKey.

toShelleySigningKey :: ShelleyWitnessSigningKey -> ShelleySigningKey
toShelleySigningKey key = case key of
  -- The cases for normal keys
  WitnessPaymentKey (PaymentSigningKey sk) -> ShelleyNormalSigningKey sk
  ..
  -- The cases for extended keys
  WitnessPaymentExtendedKey (PaymentExtendedSigningKey sk) -> ShelleyExtendedSigningKey sk
  ..

As the makeShelleySignature function above shows, an extended signing key doesn’t have to be converted into a normal signing key to witness a transaction. This is not the case for extended verification keys, though. So, inside the makeShelleyKeyWitness function we make the case distinction between normal and extended keys. In case an extended signing key is used, the verification key is derived as follows:

getShelleyKeyWitnessVerificationKey (ShelleyExtendedSigningKey sk) =
      (Shelley.coerceKeyRole :: Shelley.VKey Shelley.Payment StandardCrypto
                             -> Shelley.VKey Shelley.Witness StandardCrypto)
    . (\(PaymentVerificationKey vk) -> vk)
    . (castVerificationKey :: VerificationKey PaymentExtendedKey
                           -> VerificationKey PaymentKey)
    . getVerificationKey
    . PaymentExtendedSigningKey
    $ sk

The conversion from VerificationKey PaymentExtendedKey to VerificationKey PaymentKey is done with castVerificationKey, a function given by the CastVerificationKeyRole class.

castVerificationKey can not only be used to convert an extended key into a normal key, but also to convert a normal key into another normal key like for example a genesis key into a payment key:

instance CastVerificationKeyRole GenesisKey PaymentKey where
    castVerificationKey (GenesisVerificationKey (Shelley.VKey vk)) =
      PaymentVerificationKey (Shelley.VKey vk)

As above example shows again, there is really no difference between the different keyroles under the hood, the names are just used to keep track of what a key is used for.

So, what are the different keyroles used for? A look at the cardano-cli helps to distinguish the different categories:

cardano-cli address key-gen

The address command is used for the payment address commands, and so combined with the key-gen command we can generate PaymentKeys, PaymentExtendedKeys or ByronKeys

Byron keys are used to witness transactions that spend from Byron addresses. As is explained in Cardano.Api.Keys.Byron, these keys come, because of a design mistake, with a 32byte chaincode used in HD derivation. So, a Byron key is very similar to a Payment extended key, resulting in a very simple castVerificationKey function:

instance CastVerificationKeyRole ByronKey PaymentExtendedKey where
    castVerificationKey (ByronVerificationKey vk) =
        PaymentExtendedVerificationKey
          (Byron.unVerificationKey vk)

cardano-cli stake-address key-gen

The stake-address command is used for commands associated with staking, and so combined with the key-gen command we can generate StakeKeys. Stake keys are used to delegate stake tied to a stake address (resp. to an address that contains not only a payment credential, but also a staking credential). For a step-by-step explanation on how to register and delegate a stake key, see here.

certificate-file stake.cert

cardano-cli node key-gen/key-gen-KES/key-gen-VRF (3 keys)

The node command is used for commands associated with operating a stake pool. For an overview of the three keyroles needed, see here

StakePoolKey, this is a very sensitive key, see here KesKey link, KES stands for Key Evolving Signature VrfKey link, key take away: every stake pool operator has its own VRF key and this VRF key decides whether or not he is elected for a specific slot

cardano-cli genesis key-gen-genesis/key-gen-delegate/key-gen-utxoused to

genesis is for the genesis block commands

GenesisUTxOKey, this is one/several key/s to spend the funds created in the genesis file see here

Addresses

CIP19

cardano node course: iog