Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
200 lines (130 sloc) 8.07 KB

SLIP-0015 : Format for Bitcoin metadata and its encryption in HD wallets

Number:  SLIP-0015
Title:   Format for Bitcoin metadata and its encryption in HD wallets
Type:    Standard
Status:  Draft
Authors: Karel Bilek <>
Created: 2015-01-12


SLIP-0015 describes a format to save Bitcoin transaction metadata (labels to accounts, transactions) in a secure way, with regard to HD wallets, especially (but not limited to) hardware HD wallets.


In myTREZOR web wallet, we need to save additional metadata, such as account labels or transaction labels. We had several goals:

  1. data should be safely saved on a untrustworthy cloud service (such as Dropbox)
  2. usage should be effortless with secure hardware wallet
  3. we should allow other applications to use the files, even when they don't support hardware wallets in general

Because we want effortless usage, we want users to be able to add metadata even when they don't have the device connected, or even when they don't actually own the device.

For this reason, we don't want to sign the changes on the secure device and we want to encrypt everything on an unsecure device, with the key in memory. This has the unfortunate consequence of attacker being able to both read and edit metadata if he attacks the unsecure device.

However, we want at least prevent the cloud storage operator to be able to read the metadata. We want to hide the metadata itself from the cloud storage operator, and even the XPUBs of the accounts for deniability.

General design

We first derive a master key from hardware device itself, which is shared for all accounts on the device.

We then derive account key for every account. This key is a string -- because of the stated goal 3., we want to be able to import it into third party applications without HD wallets.

From the account key, we derive both a filename and a symmetric encryption key. We then save the metadata to the given file, in an encrypted JSON.

Design details

Deriving master key

We first get the master key by sending CipherKeyValue to hardware device with following parameters

  • path: m/10015'/0' (hardened path, see BIP32)
  • key: Enable labeling?
  • value: fedcba98765432100123456789abcdeffedcba98765432100123456789abcdef (byte sequence, here in hexadecimal)
  • encrypt: true
  • ask_on_encrypt, ask_on_decrypt: true
  • iv: unset

CipherKeyValue is defined in SLIP-0011.

The master key should be 32 bytes (256 bits) long. It is treated as a pseudo-random byte sequence.

Deriving account key

From the master key, we derive the account key for every account in the following way:

First, we use the HMAC function:

HMAC-SHA256(master key, xpub)


  • master key is a byte sequence, as defined in the previous section
  • xpub is a string, as defined in BIP32. For example:


Then, the result is converted to string using Base58Check encoding, as used in Bitcoin.

The API key is either 49 or 50 characters long.

Deriving filename and password from account key

We take the account key, as a string, and we use HMAC function to derive filename and password for metadata file. Every account has its own metadata file.

  • First, we use the HMAC function HMAC-SHA512(API key, constant), where

    • API key is a string (in base58c) from the previous section.

      The API key is taken as a string, so third-party applications can use their own API keys.

    • constant is 0123456789abcdeffedcba9876543210 (byte sequence, here in hexadecimal).

  • The result is 64 bytes/512 bits.

    • The first half is used to derive the filename.

      The bytes are converted to hexadecimal, which is the used as a filename, with the extension ".mtdt".

      We are using hexadecimal instead of base64/base58 because of the ambiguity on case-insensitive filesystems.

    • The second half is used as a key for further encryption, as a byte sequence.

  • We are using AES-256-GCM algorithm for encryption.

    • Random 12 bytes are generated as a IV
    • GCM is used with the full 128-bit tag
  • The resulting file looks like this:

    • first 12 bytes of the file are the random IV
    • the next 16 bytes are the GCM authentication tag
    • the rest is the ciphertext

Data format

The (decrypted) metadata are in following format:

The file is a serialized JSON object with the following keys:

  • version: version of metadata format, for future backwards compatibility. The version is currently 1.0.0.
  • accountLabel: label for the account, a string
  • outputLabels: labels for outputs, described further
  • addressLabels: labels for addresses, described further

outputLabels has transaction hashes for keys, and for values it has object with output indexes for keys and output labels, as strings, for values. Output indexes start at 0.

addressLabels has addresses (in traditional Base58Check encoding) for keys and their labels for values. Only receiving addresses are saved in this object.

All labels can have any unicode letters. Empty string is treated in the software as having no label.

An example object looks like this:

  "version": "1.0.0",
  "accountLabel": "Saving account", // one file per account, so only 1 label needed
  "addressLabels": {
    "1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL": "My receiving address",
    "1GWFxtwWmNVqotUPXLcKVL2mUKpshuJYo": ""  // equivalent to no label set or null
  "outputLabels": {
    "350eebc1012ce2339b71b5fca317a0d174abc3a633684bc65a71845deb596539": {
      "0": "Money to Adam",
      "1": ""  // equivalent to no label set
    "ebbd138134e2c8acfee4fd4edb6f7f9175ee7b4020bcc82aba9a13ce06fae85b": {
      "0": "Feeding bitcoin eater"

(comments are of course not part of a valid JSON and are included here only for clarity)


All the example code is in Python2.

Deriving "master" key

Example code, deriving a master key from a connected TREZOR is in It requires python-trezor installed and TREZOR connencted

For the "stress test" wallet, defined in SLIP-0014, the master key should be (in hex)::


Deriving "account" key

Example code, deriving an account key for master key, is in First argument of the script is xpub of the account, the second argument is the master key from previous step (in hexadecimal).

For the "stress test" wallet, defined in SLIP-0014, and its first account (with the xpub xpub6BiVtCp...), the key should be::


Deriving filename, decoding

Example code for decryption is in First and only argument is the account key from previous step. The file has to be in a current working directory (in myTREZOR, we use ~/Dropbox/Apps/TREZOR/ for saving the files).

With the key v5kCxSKLTsnwmgPBeaRyFDWeG9zXouF34L72763zjLrS4LWy8, filename 08108c3a46882bb71a5df59f4962e02f89a63efb1cf5f32ded94694528be6cec.mtdt and the data (in hex)


we should get to file, similar to the one described above.

Similarly, in there is an example code for encrypting.

You can’t perform that action at this time.