Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Latest commit

Β 

History

History
282 lines (199 loc) Β· 13.1 KB

lip-0067.md

File metadata and controls

282 lines (199 loc) Β· 13.1 KB
LIP: 0067
Title: Introduce a generic keystore
Author: Maxime Gagnebin <[email protected]>
Discussions-To: https://research.lisk.com/t/introduce-a-generic-keystore/360
Status: Active
Type: Informational
Created: 2022-06-28
Updated: 2024-01-04

Abstract

We describe a format for encrypted information to be used in the Lisk ecosystem. This could be used in the wallet to encrypt a user's private keys or by the block generator module to store the generator keys.

Copyright

This LIP is licensed under the Creative Commons Zero 1.0 Universal.

Motivation

A common encryption standard allows different wallets and third party tools to be compatible with each other.

Rationale

The Lisk protocol uses different types of signature schemes for different use cases. For example, transactions must be signed by the account sending it using an Ed25519 signature and commits must be signed using a BLS signature. The proposed keystore is agnostic to the private key type and could allow user facing products to abstract away the signature type from the user. The way private keys are generated from secret recovery phrases is specified in LIP 0066.

Encrypting Secret Recovery Phrases

The secret recovery phrase is a sequence of 12 (or 24) words that follow the BIP 39 standards and that is used to derive all private keys a user might need. Naturally, it is the first thing to be generated and shared with the user to be stored safely. However, it is possible that users lose their phrase and need to back it up once more. For this reason, the keystore proposed below can easily be used to encrypt and store secret recovery phrases in a file. Each file has an associated password which is used to derive the encryption key using a key-derivation function.

In the same encrypted file, we can store metadata indicating how the secret recovery phrase was used and which private keys were already generated with it. This allows users to not only recover all their accounts when importing the encrypted file in a new device, but also to make sure that newly generated private keys are using a new derivation path.

Encrypting Private Keys

The keystore presented below is also designed to encrypt private keys. The reason to encrypt and store private keys directly is two fold. First it improves the efficiency of the signing process. Indeed, if we decrypt the private key directly, there is no need to derive the key again from the secret recovery phrase. Secondly, in the case the device of the user was corrupted, decrypting just one private key would compromise the account linked to this private key, but not the others generated with the same secret recovery phrase.

Specification

The specifications below are inspired from Web3 Secret Storage with the addition of a metadata property which allows to store all needed information regarding the encrypted material.

Encryption File Format

We store secret recovery phrases and private keys in a JSON file following the format below.

keystoreSchema = {
  "type": "object",
  "required": ["crypto", "id", "version"],
  "properties": {
    "crypto": {
      "type": "object",
      "properties": {
        "cipher": {"type": "string"},
        "cipherparams": {"type": "object"},
        "ciphertext": {"type": "string"},
        "kdf": {"type": "string"},
        "kdfparams": {"type": "object"},
        "mac": {"type": "string"}
      }
    },
    "id": {"type": "string"},
    "version": {"type": "integer"},
    "metadata": {
        "name": {"type": "string"},
        "description": {"type": "string"},
        "pubkey": {"type": "string"},
        "address": {"type": "string"},
        "path": {"type": "string"},
        "derivedFromID": {"type": "string"},
        "creationTime": {"type": "string"},
        "pathsUsed": {"type": "array"},
        "tags": {"type": "array"}
    }
  }
}

In the following sections, we describe the uses of the properties of keystoreSchema.

crypto

cipher and cipherparams

The specified function encrypts the message using the encryption key; to decrypt it, the encryption key along with cipher and cipherparams must be used. If the encryption key is longer than the key size required by the encoding function, it is truncated to the correct number of bits. The following option is supported:

cipher function cipherparams Definition
"AES-128-GCM" aes-128-gcm {iv: string, tag: string} RFC 7714
ciphertext

The encrypted message in hexadecimal format. It is generated by inputting the message and the encryption key to aes-128-gcm function.

kdf and kdfparams

A key derivation function is used to derive an intermediate key, derivedKey, from the user password. The derived key is used to generate the encryption key for decryption, and verify if the given password is correct. The encryption key is the leftmost 16 bytes of the derived key. The function, and the params used to get the derivation key from the password are specified in kdf. The following values of kdf and kdfparams are allowed, depending on the key-derivation function:

kdf function kdfparams Definition
"PBKDF2-SHA-256" pbkdf2 {iterations: uint32, salt: string} RFC 2898
"argon2id" argon2id {parallelism: uint32, iterations: uint32, memory: uint32, salt: string} RFC 9106
mac

Computed as SHA256(derivedKey[-16:] + ciphertext). The mac should be used to verify that the password for the key derivation function was correct. This is done by computing the mac based on the derived key and ciphertext. If this mac does not match with the one in the JSON file the password was not correct.

id

The id property stores a provided uuid (version 4 UUID as specified in RFC 4122), this is a randomly generated ID. It is used if the keystore needs to be referred to. Implementation help: ID generation is supported by nodejs with the uuid package, for example.

version

The version is set to "3".

metadata

All information that is useful when using the file. None of the properties are required and they can be left empty depending on the usage of the encrypted file. Other properties could also be included in the metadata property, but they might not be supported by other implementations of this proposal.

name

A name given by the user to allow easier identification of the file.

description

The description field indicates the nature of the encrypted material. We specify the following description for commonly encrypted messages in Lisk:

Description value Uses
"Secret recovery phrase" The description for secret recovery phrases.
"Ed25519 private key" The description for derived Ed25519 private key, encoded as a hex string for encryption.
"BLS private key" The description for derived BLS private key, encoded as a hex string for encryption.

Other descriptions could also be possible, but do not need to be supported by products implementing this proposal.

pubkey

The public key of the key pair. This property is only used if the encoded data is an Ed25519 private key or a BLS private key.

address

The address corresponding to the key pair. This property is only used if the encoded data is an Ed25519 private key.

path

The path used to derive the key pair from the secret recovery phrase. This property is only used if the encoded data is an Ed25519 private key or a BLS private key.

derivedFromID

This property contains the UUID of the file encrypting the corresponding secret recovery phrase.This property is only used if the encoded data is an Ed25519 private key or a BLS private key.

creationTime

Time when the file was created.

pathsUsed

List of paths used with the store recovery phrate to derive key pairs. This property should be used only if the encoded data is a secret recovery phrase. This information is useful to recover all accounts that were generated with this recovery phrase. It is also useful when creating a new account and selecting the next unused path.

tags

List of tags associated with the file.

Recommended Parameters

Argon2id

We recommend using argon2id (instead of PBKDF2) to derive the encryption key, as it is recognised as a more secure key-derivation function (see for example OWASP recommendations). We recommend to follow RFC 9106 for basic parameter choices. Whenever possible, in particular on a block generating node, we recommend the values

  • iterations=1,
  • parallelism=4 lanes,
  • memory=2097023 KiB,
  • 16 bytes salt,
  • 32 bytes output

which correspond to the first recommended option of RFC 9106, except for the memory value. The chosen memory value is slighly smaller than the recommended 2 GiB such that it works also with libraries like hash-wasm which fails for 2 GiB.

Argon2id for Wallets

As some mobile devices have less memory, we recommend to use the second recommended option of RFC 9106 which requires less memory but uses more iterations instead:

  • iterations=3,
  • parallelism=4 lanes,
  • memory=65536 KiB (64 MiB),
  • 16 bytes salt,
  • 32 bytes output.

Password Strength General Recommendations

The password submitted by the user should be validated to be long enough and use a variety of numbers and lower and upper case letters (see for example https://www.securden.com/blog/top-10-password-policies.html). Further password requirements can be found in EIP 2335.

PBKDF2

Using PBKDF2 as a KDF is currently implemented in Lisk Elements with 10^6 iterations. PBKDF2 is considered secure, but slightly less future proof than argon2id.

Backwards Compatibility

There are no incompatibilities since the protocol is not changed.

Reference Implementation

Appendix

Help for Implementation

This audit can help to create a better implementation https://github.com/trailofbits/publications/blob/master/reviews/ETH2DepositCLI.pdf

Examples

Secret Recovery Phrase

Password: testpassword. Secret recovery phrase: target cancel solution recipe vague faint bomb convince pink vendor fresh patrol.

{
  "crypto": {
    "cipher": "aes-128-gcm",
    "cipherparams": {
    "iv": "da7abd52538d936db5eb95d7b2b48cca",
    "tag": "9131d1737229bba12edbc0de1fe2c1f1"
    },
    "ciphertext": "55b03a67428dc9d43331221c55775ead6cf4a5575dfc37d6d72a59750d92d6cd803788ef905d48ce08789ec53198ef08c5b82af0dac100ca07f9ecbad361974552fd8c9aed8c8fb6f65ddafa6efba837",
    "kdf": "argon2id",
    "kdfparams": {
      "parallelism": 4,
      "iterations": 1,
      "memory": 2048,
      "salt": "9cf89f89fead59b4f03a1164b47bed2f"
    },
    "mac": "87d989d9681446268c70aa8d22474013220363bfabab6f4d06fcf31e6d9ebfef"
  },
  "id": "fa3e4ceb-10dc-41ad-810e-17bf51ed93aa",
  "version": "3",
  "metadata": {
    "name": "Maxime",
    "description": "Secret recovery phrase",
    "pathsUsed": "m/44'/134'/0'"
  }
}

Ed25519 Key Pair

Password: testpassword. Key pair derived from the secret recovery phrase above, and the path m/44'/134'/0'.

{
  "crypto": {
    "cipher": "aes-128-gcm",
    "cipherparams": {
    "iv": "da7abd52538d936db5eb95d7b2b48cca",
    "tag": "9131d1737229bba12edbc0de1fe2c1f1"
    },
    "ciphertext": "55b03a67428dc9d43331221c55775ead6cf4a5575dfc37d6d72a59750d92d6cd803788ef905d48ce08789ec53198ef08c5b82af0dac100ca07f9ecbad361974552fd8c9aed8c8fb6f65ddafa6efba837",
    "kdf": "argon2id",
    "kdfparams": {
      "parallelism": 4,
      "iterations": 1,
      "memory": 2048,
      "salt": "9cf89f89fead59b4f03a1164b47bed2f"
    },
    "mac": "87d989d9681446268c70aa8d22474013220363bfabab6f4d06fcf31e6d9ebfef"
  },
  "id": "ef52c117-d7cc-4246-bc9d-4dd506bef82f",
  "version": "3",
  "metadata": {
    "name": "my lisk account",
    "description": "Ed25519 private key",
    "pubkey": "c6bae83af23540096ac58d5121b00f33be6f02f05df785766725acdd5d48be9d",
    "address": "ed629c34f72e276ba38be61b6f289f84627f2b81",
    "path": "m/44'/134'/0'",
    "derivedFromID": "fa3e4ceb-10dc-41ad-810e-17bf51ed93aa"
  }
}