This article will go over how we were able to redeem incentivized testnet (ITN) rewards for a Cardano community member who had lost their wallet mnemonic but still had access to the ITN Daedalus wallet file and spending password.

TL;DR

We extracted the private root key from the ITN wallet file and decrypted it with the spending password. With the decrypted private root key, we derived the corresponding reward account key. Finally, the ITN rewards were redeemed on mainnet manually using the command-line interface (CLI) to the Cardano-Node software.

Infographic

The Scenario

Many Cardano community members participated in the Incentivized-Test-Network (ITN) during the first part of 2020. The rewards earned on the ITN were real, and IOHK promised to distribute them on the main network (mainnet) with the release of the Shelley era in August 2020. The wallet’s mnemonic is required to redeem rewards earned from an ITN wallet using the mainnet wallet software (e.g., Daedalus and Yoroi). We put together a tutorial on this here. Unfortunately, some in the community lost the mnemonic used to create their ITN wallets.

However, these community members still had their wallet files and corresponding spending passwords. We knew it should be theoretically possible to recover the rewards with only this information and we set out to solve this issue.

The Daedalus Wallet File

Daedalus uses a SQLite file to store wallet information. The image below shows the structure of an ITN Daedalus wallet file viewed with DB Browser for SQLite.

Cardano-Wallet-DB-Browser-Structure

The database contains a table called private_key, which itself includes a field called root. The root field contains the wallet’s private root key as a string of hexadecimal characters. It’s important to understand that this is not the actual root private key for the wallet but an encrypted version. The decryption key is the wallet’s spending password. More correctly, this string contains the private key, public key, and chain-code (used during the process of deriving child keys), all concatenated together. Only the private key is encrypted.

Cardano-Wallet-DB-Browser-Data-private_key

We can easily extract the encrypted root private key from the Daedalus wallet file with a bit of Python code.

import sqlite3
wallet_fpath = "/path/to/wallet/file.sqlite"
con = sqlite3.connect(wallet_fpath)
cur = con.cursor()
cur.execute("SELECT root FROM private_key")
key = cur.fetchone()[0]

Note that Daedalus runs SQLite in WAL mode, and therefore some auxiliary files are present in the same directory as the main .sqlite file. It’s best to leave these with the main file during this process in case sqlite3 needs to access them.

The Root Private Key

Before deriving the reward account keys, we must decrypt the private root key obtained from the wallet file. After digging into the cardano-wallet software, we discovered that a second library called cardano-crypto handles the encryption. Upon further investigation, it became evident that the encrypt/decrypt functionality is completely contained in a small set of code written in C. The complete set of C functionality is actually a combination of C code from cardano-crypto and another “Haskell” library called cryptonite. Similar to cardano-crypto, cryptonite is a Haskell wrapper around C code that performs the core cryptography.

All-c-code

The function we identified that does the decryption was unencrypt_start, defined in the file encrypted_sign.c, and its definition is shown below.

static void unencrypt_start
    (uint8_t const* pass,
     uint32_t const pass_len,
     encrypted_key const *encrypted_key /* in */,
     ed25519_secret_key decrypted_key /* out */)

With this decryption function identified, we needed to develop a small C interface that transformed the hex data from the wallet file to a byte array suitable for the decryption algorithm.

Post decryption, we have an array of 128 bytes (256 characters when converted to a hex string) that contains the unencrypted private key (64 bytes), the public key (32 bytes), and the chain-code (32 bytes). The chain-code is used during the cryptographic process of deriving the child keys.

Experiment

In order to verify that our process was correct, we conducted the following experiment using the cardano-wallet application. We used the Jormungandr version since we were dealing with an ITN wallet; however, it should be the same on mainnet.

Create a new 15-word recovery phrase (like what was used during the ITN).

cardano-wallet-jormungandr recovery-phrase generate --size 15 

whip sister deal treat upper large desk wish fatal hundred security enlist animal ensure desert

Using the mnemonic, create a new wallet file and secure it with a password.

cardano-wallet-jormungandr wallet create from-recovery-phrase experimental_wallet

Then also use the mnemonic to generate a private root key directly.

cardano-wallet-jormungandr key from-recovery-phrase Jormungandr <<< "whip sister deal treat upper large desk wish fatal hundred security enlist animal ensure desert"

The above command results in the following output.

xprv1arl334v894glt566fss83tx3w39h66720lfcdqln762gxngsc4fddzrk3hfyt2r5s2ddhg86u7e7u3ln2fglcayhsh9zjlv7uyh0m5aee3tae7af4g2ljq2vasmwz7dt298lxv76e2q6ccq57knte9s42uaxkkva

We then compared the above private key to our code’s output when run with the wallet file generated from the mnemonic. The results exactly matched when using the bech32 library (also part of the cardano-wallet software suite) to encode the decrypted private key and chain code concatenated together while specifying xprv as the bech32 header.

The Staking Key Files

With the decrypted root private key, we can derive the reward account keys using the cardano-address application (also part of the cardano-wallet suite).

cardano-address key child 1852H/1815H/0H/2/0 <<< xprv1arl334v894glt566fss83tx3w39h66720lfcdqln762gxngsc4fddzrk3hfyt2r5s2ddhg86u7e7u3ln2fglcayhsh9zjlv7uyh0m5aee3tae7af4g2ljq2vasmwz7dt298lxv76e2q6ccq57knte9s42uaxkkva

The input 1852H/1815H/0H/2/0 is referred to as the “derivation path.” Daedalus creates what are referred to as HD (Hierarchical Deterministic) wallets. These wallets have a single root key derived from a mnemonic that can then be used to derive an infinite number of wallets (even containing different types of coins!). The derivation path varies for different accounts i.e., reward accounts on the Shelley network use a 2 in the second to the last derivation path variable (as opposed to spending accounts). This is a fascinating topic, and we refer the interested reader to the following resources:

The derived staking keys need to be formatted as CBOR hex for compatibility with the mainnet cardano-node. We do this by decoding the Bech32 encoded keys and manually concatenating the private key, public key, and chain-code. This combined key is placed in the following format and saved to file (stake.skey).

{
    "type": "StakeExtendedSigningKeyShelley_ed25519_bip32",
    "description": "",
    "cborHex": "5880b0d654746083475f6b1a516ab2b967b6726f55c1fc1ba1badcb1d4395f10c552972cca0922bca34bf152bf349377990099b65d91f030c61feaf3509efa1be55a7b2c9e669a829b355cf168eb1df8e8e212fd5e28c448182637932f3291ccb3164478ff58cc96dd8ef6bce08774f9b89c6624a2d89fdf79581e85e395e7f20a91"
}

At this point, the contributions from our friend Pal Dorogi at UNDR stake pool were invaluable. He has put together an excellent tutorial on how to derive staking keys from a mnemonic. Working from his tutorial, we can generate the reward account public key and address using the cardano-cli.

cardano-cli key verification-key --signing-key-file stake.skey --verification-key-file stake.evkey
cardano-cli key non-extended-key --extended-verification-key-file stake.evkey --verification-key-file stake.vkey
cardano-cli stake-address build --stake-verification-key-file stake.vkey --mainnet > stake.addr

The following cardano-cli command may then query the balance of the reward account (ITN rewards!).

cardano-cli query stake-address-info --mainnet --address `cat stake.addr`

The cardano-tools python library also provides a method for querying a rewards account. An example is shown in the following section.

The Rewards

Redeeming the rewards is easy using our Cardano-Tools python library. An example script is given below.

from pathlib import Path
import json
import sys

from cardano_tools import ShelleyTools

# Inputs
working_dir = Path("/home/lovelace/cardano-wallets")
rewards_addr = open(working_dir / "rewards_stake.addr", 'r').read()
rewards_skey = working_dir / "rewards_stake.skey"

# Create a ShelleyTools object
shelley = ShelleyTools(
    "/usr/local/bin/cardano-cli",
    "/home/lovelace/cardano-node/db/node.socket",
    working_dir / "temp"
)
shelley.debug = True # If True, more diagnostics will be printed

# Calculate the amount of rewards to redeem.
rewards = shelley.get_rewards_balance(rewards_addr)/1_000_000
print(f"Rewards in {rewards_addr} = {rewards:,.2f} ADA")

# Addresses
payment_addr = open(working_dir / "rewards.addr", 'r').read()
payment_skey = working_dir / "rewards.skey"

# Claim the rewards to a spending wallet.
shelley.claim_staking_rewards(
    rewards_addr,
    rewards_skey,
    payment_addr, # <- this is where the rewards will be sent
    payment_skey,
    offline=True  # <- if true, will not send tx (offline)
)

Another example of redeeming staking rewards using the Cardano-Tools library is available here.

Conclusion

In this article, we described how we were able to redeem ITN rewards using only a Daedalus wallet file and spending password — without the wallet’s mnemonic. We hope that this will be helpful to other community members.

If you have questions about staking or anything else related to Cardano, feel free to join our Telegram channel. Follow us on Twitter and Reddit to be notified of future posts!