Wallet Server Overview

The Wallet Server API aims to solve two problems:

  1. Synchronizing wallets across different clients/devices
  2. Maximizing security in consideration of usability and practicality

We aim to accomplish this by mixing strong encryption and traditional login-based security in order to get the advantages of each.

Basics

User Credentials

The security is based around providing the user with two credentials:

  1. backupKeyphrase
    The backupKeyphrase is a 160-bit random number generated by the client when creating a new wallet.

    It can be represented in any way so long as the client can interpret it. However, for interoperability, we define some standard ways of representing it:

    • When displayed as text it should be converted to an encodedKeyphrase:

      encodedKeyphrase = base58(keyphraseSpec)
      keyphraseSpec    = 8F versionByte backupKeyphrase miniChecksum
      miniChecksum     = substr(keyphraseHash, 0, 2))
      keyphraseHash    = sha256(backupKeyphrase)

      The version byte for version 1 of the standard shall be 0x00. The encoding is chosen to look different than a Bitcoin address in order to avoid confusion.

      Example:

      backupKeyphrase  = D7B199EB8BD3E23F1ACCB2B138F1706FC78C0AFA16
      keyphraseHash    = FC3000902542C82846F7FF37F57B8D8BC9292B6942CD9B30BA8F3CCC2EAE915816
      keyphraseSpec    = 8F                         // Identifier
                         00                         // Version Byte
                         D7B199EB 8BD3E23F 1ACCB2B1 // Keyphrase
                         38F1706F C78C0AFA
                         FC30                       // Checksum
      encodedKeyphrase = "E38dyTYsR7i6Gd8SJsmKd9du92MPvEXV9"
      
    • It can also be represented as a URL in the format

      walletUrl = "bjswallet://" walletHost "/" encodedKeyphrase

      Example:

      bjswallet://wallet.webcoin.ch/E38dyTYsR7i6Gd8SJsmKd9du92MPvEXV9
    • The URL can be used to create a QR code.

      Example:

      Wallet as QR-Code

    The backupKeyphrase MUST NEVER be stored in persistent memory on a device that is connected to a network and/or can run arbitrary code. It provides full, unrestricted access to the corresponding wallet.

    It SHOULD be stored in printed form or on a USB stick in a secure location. The user should choose a location where he would feel comfortable storing an equivalent amount of cash. For a small Bitcoin wallet, the printed code could be stored in a physical wallet, for a larger amount it could be stored in a safe or safety deposit box.

    Clients MAY also offer other highly secure storage such as hardware-based security dongles, smart cards, etc.

  2. pin
    The pin by default is a random string matching the regular expression [a-z2-9]{6} and is generated by the wallet server. The wallet server MAY choose a different default length and/or character set and MAY allow its users to manually change the pin.

    e.g. tha8ec

    The pin is intended to authenticate the user during day-to-day use. It is not long enough to be cryptographically strong, but is strong enough as a login password together with server-side measures such as limiting the number of attempts and disabling pin-based access in case of a large scale attack.

    The server MAY implement whatever policies it wants with respect to the pin, however they should be such as to defend against both a small-scale attack (single client’s accessKey compromised)

Derived Credentials

  1. masterKey
    The masterKey is derived from the backupKeyphrase using ANSI X9.63-KDF with the following parameters:

    Hash
    SHA256
    SharedInfo
    72f57f2f9ed68aa0d46d460d33bf66a267cc382d16
    keydatalen
    256
  2. cstoreKey
    The cstoreKey is stored on the server and can be requested by the client using accessKey + pin.

Other derived credentials are:

  • accessKey
    sha2562(“walletid” masterKey)
    This is what the server uses to identify wallets. It is stored on both server and client.

  • passKey
    sha2562(“walletpass” masterKey)
    This is what the server uses to authenticate wallet-changing requests. A hash of it is stored on the server. It MUST NOT be stored on the client.

Stored Information

Wallet Server

The wallet server stores the following items for each wallet:

  • accessKey
    sha2562(“walletid” masterKey)
    This is what the server uses to identify wallets.

  • (Some hash of) passKey
    sha2562(“walletpass” masterKey)
    The server should store a salted hash of this value.

  • cstoreKey
    Server-generated, random 256-bit key
    This is used by the client to decrypt the cstore, see section “CSTORE path” below for details.

  • walletAdresses[]
    This contains the actual wallet information as an array of JSON objects. Each object represents a key and associated metadata:

    {
      "priv": "enc!uXAMSAla6LwMSAlYixQk0fqBwogMSAmJFCTDagJV/zU=",
      "pub": "!BHi5G6SazJRzTZeppFcLv/yBHqcvrBJdhUl4+NDv0ri1khxLGO+QeXXj0KFwjulNmnBKU0ZR96hpHgFY55wiSxA=",
      "desc": "!Bitcoin Faucet"
    }

    Each keyInfo object can only contain strings.

    Each string starts with an identifier denoting its format:

    • ! - Unencrypted, standard string
    • enc! - Following data is encrypted using masterKey and encoded as base64
    • b64! - Following data is encoded as base64

    Clients MUST ignore keyInfo fields they don’t know or that use an unknown encoding identifier.

    Private keys MUST be encrypted by default, however they can be unencrypted if the user so chooses.

    Public keys SHOULD be unencrypted by default. Note that wallet servers may impose a minimum balance or limit storage space based on balance, encrypted public keys will not count towards the balance in that case. The server MAY reject encrypted public keys.

Client

The client stores the following items for each wallet. Clients MAY support multiple wallets.

  • accessKey
    sha2562(“walletid” masterKey)
    This is what the server uses to identify wallets.

  • cstoreBox
    aes256(key=cstoreKey, data=masterKey)
    This is used by the client to turn the cstoreKey sent by the server after login into the masterKey needed to decrypt the private parts of the wallet.

  • walletAddresses[]
    Same as on server. See above.

Server API

  • wallet/create
    Server will receive a new wallet. Note that this will create an empty wallet, the actual data will be uploaded later using wallet/add.

    Request:

    • accessKey - sha2562(“walletid” masterKey)
    • passKey - sha2562(“walletpass” masterKey)
    • cstoreKey - 256-bit random key

    Response:

    • pin - User login password

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • InvalidPassKey - The passKey does not follow the correct format
    • InvalidCstoreKey - The cstoreKey does not follow the correct format
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/login
    Authenticate the pin for a wallet.

    Request:

    • accessKey - Stored on client
    • pin - User-entered

    Response:

    • cstoreKey - Allows client to decrypt cstoreBox, providing masterKey.

    Comment:

    The wallet may be locked after a number of failed login attempts.

    “Locking a wallet” means that wallet/login will only respond with the WalletLocked error until wallet/access is called.

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • InvalidPin - The pin was incorrect
    • InvalidPinLocked - Too many wrong pins entered, account has been locked
    • WalletLocked - The account was already locked
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/access
    Get the current pin for a wallet and reset the number of pin attempts.

    Request:

    • accessKey - Derived from backupKeyphrase
    • passKey - Derived from backupKeyphrase

    Response:

    • pin - Current pin

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • InvalidPassKey - The passKey does not follow the correct format
    • IncorrectPassKey - The passKey did not match the one on file
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/add
    Upload new keyInfo objects for this wallet.

    Request:

    • accessKey - Stored on client
    • passKey - Derived from masterKey (client must be active in order to create new keys)
    • walletAddresses - Array of keyInfo objects

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • InvalidPassKey - The passKey does not follow the correct format
    • IncorrectPassKey - The passKey did not match the one on file
    • RejectData - The server rejects the keyInfo objects (reason provided in error messge)
    • QuotaExceeded - The wallet is too large, the client should re-use existing keys and/or display an error.
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/download
    Download the walletAddresses for this wallet.

    Request:

    • accessKey - Stored on client
    • passKey - Can be derived from masterKey which is derived from backupKeyphrase

    Response:

    • walletAddresses - Array of keyInfo objects

    Error Conditiions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • InvalidPassKey - The passKey does not follow the correct format
    • IncorrectPassKey - The passKey did not match the one on file
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/changepin
    Change the pin by request of the user.

    Request:

    • accessKey - Stored on client
    • passKey - Derived from masterKey, which is available from either the old pin or the backupKeyphrase
    • pin - Desired pin

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • InvalidPassKey - The passKey does not follow the correct format
    • IncorrectPassKey - The passKey did not match the one on file
    • PinChangeNotAllowed - This server does not allow custom pins
    • InvalidPin - The pin was not accepted by the server
    • InvalidPinChars - The pin contains characters that are not part of the acceptable set of characters
    • InvalidPinTooShort - The pin is too short
    • InvalidPinTooLong - The pin is too long
    • InvalidPinNotSecure - The server thinks the pin is not secure enough (servers MAY check pins against a password dictionary)
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/lock
    Intentionally lock a wallet.

    Request:

    • accessKey - Stored on client

    Comment:

    This is equivalent to send several requests to wallet/login with false pins and may be useful if the accessKey is known to be compromised. If that is the case the client SHOULD call wallet/lock, immediately followed by creating a new wallet and allowing the user to transfer his/her Bitcoins over to the fresh wallet.

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • ServerError - A server-side error occurred, the client should try again later
  • wallet/delete
    Delete a wallet from the server.

    Request:

    • accessKey - Stored on client
    • passKey - Derived from masterKey, which is available from either the old pin or the backupKeyphrase

    Comment:

    The server MUST offer this function to allow users to delete any unencrypted personal information stored on this server.

    When the server is no longer trusted, the client MUST generate a new backupKeyphrase. The client SHOULD offer the user the option to transfer his Bitcoins to a fresh address or set of addresses. However the old addresses SHOULD remain part of the new wallet, unless otherwise directed by the user.

    After a wallet is deleted, the server should respond with UnknownAccessKey to requests concerning that wallet.

    Error Conditions:

    • InvalidAccessKey - The accessKey does not follow the correct format
    • UnknownAccessKey - No wallet found for this accessKey
    • InvalidPassKey - The passKey does not follow the correct format
    • IncorrectPassKey - The passKey did not match the one on file
    • ServerError - A server-side error occurred, the client should try again later

Algorithms

Creating a new wallet

  1. Generate backupKeyphrase, a random integer from 0 to 2160-1.

  2. Generate cstoreKey, a random integer from 0 to 2256-1.

  3. Derive masterKey, accessKey from backupKeyphrase.

  4. Derive passKey from masterKey.

  5. Encrypt masterKey using cstoreKey, producing cstoreBox.

  6. Call API wallet/create. Server responds with pin.

  7. Create a new array walletAddresses.

  8. For i in 1..n do

    1. Generate an ECC private key.
    2. Derive the public key from the private key.
    3. Encrypt the private key using masterKey.
    4. Create a JSON object keyInfo containing the public key and the encrypted private key.
    5. Add keyInfo to the walletAddresses Array.
  9. Call API wallet/add to upload walletAddresses.

  10. Save accessKey, cstoreBox, walletAddresses to disk.

  11. Display backupKeyphrase and pin to user.

The masterKey is now in memory for the duration of the session.

Unlocking a wallet using the pin

  1. Let user input pin.
  2. Call API wallet/login. Server responds with cstoreKey.
  3. Decrypts masterKey from cstoreBox using cstoreKey.

The masterKey is now in memory for the duration of the session.

Retrieving a wallet online using the backupKeyphrase

This is usually used to install an existing wallet on a new client. Another case could be to restore the wallet after a hard drive crash, lost cell-phone, etc.

  1. Let user input backupKeyphrase (as text or from QR.)
  2. Derive masterKey, accessKey from backupKeyphrase.
  3. Derive passKey from masterKey.
  4. Call API wallet/access. Server responds with pin.
  5. Call API wallet/download. Server responds with walletAddresses.
  6. Encrypt masterKey using cstoreKey, producing cstoreBox.
  7. Save accessKey, cstoreBox, walletAddresses to disk.
  8. Display pin to user.

The masterKey is now in memory for the duration of the session.

Retrieving a wallet offline using the backupKeyphrase

In case the wallet server is no longer accessible, the user can use this procedure to restore his/her wallet locally.

  1. Let user input backupKeyphrase (as text or from QR.)
  2. Derive masterKey from backupKeyphrase.

The masterKey is now in memory for the duration of the session.

Lazy authentication

Clients SHOULD be able to operate as much as possible without authentication.

The following operations SHOULD NOT require activating the wallet:

  • Display balance
    If some public keys are encrypted (their data starts with “enc!”) the client SHOULD mark the balance in square brackets to denote that it may not be the full balance available and update it once the wallet is unlocked. It SHOULD also provide a help icon, notification, tooltip or some other way for the user to find out what the square brackets mean.

  • Display address book information
    The client SHOULD let the user browse the address book where that information is not encrypted.

  • Create new addresses
    The client SHOULD try and have a small contingent of extra addresses that it can display to the user. If that reserve runs out the client MUST require the user to active the wallet before new addresses are generated.

  • Queue actions
    The client SHOULD allow the user to enter transactions and add them to a queue for later transmission. This is useful for both when the user can’t or currently doesn’t want to login and for when there is no Internet connection available.

    The client should clearly display the number of queued actions and, on request, list the exact actions that are queued to prevent unauthorized users from “sneaking” transactions into the queue.