ERC-2335 BLS12-381 keystore
Review · Accounts & wallets · created 2019-09-30
A standard for encrypting a BLS12-381 private key with a password and storing it as a single JSON file so it can be safely exchanged between devices.
Why it is needed
On Ethereum, users must hold their own keys (private keys) directly, so how keys are safely stored and transferred is central to user experience. Previously, a keystore format called Web3 Secret Storage filled this role, but it had two problems. First, it uses the Keccak256 hash to compute verification values. Since BLS signatures and keys are a standard shared across multiple chains, they should not be biased toward any one chain, but Keccak is a strongly Ethereum-flavored choice and is not neutral. Second, the existing format grew by having features bolted on as needed, so it lacks structural abstraction, making it hard to swap or simplify cryptographic components. This standard defines a new keystore format for BLS12-381 keys that uses a chain-neutral hash and abstracts each cryptographic step into a module.
In depth
Module abstraction
All cryptographic components of the keystore are expressed using the same framework, called a module. A module consists of three fields: function, which indicates which cryptographic construction is used; params, the parameters required by that function; and message, the function's main input. Under the crypto field there are three modules, kdf, checksum, and cipher, which handle deriving the decryption key, verifying the password, and decrypting the secret respectively. This means that when a component needs to change, only that module needs to be replaced without affecting the rest of the spec. The Rationale explains that treating the KDF, checksum, and cipher as instances of the same thing allows them to be swapped out with minimal effort.
Password handling rules
The password is an arbitrary Unicode string. It is first converted to NFKD representation, control characters are removed, and it is then encoded as UTF-8. The characters to be removed are the C0 control codes (0x00-0x1F), the C1 control codes (0x80-0x9F), and the Delete character (0x7F). Space (0x20) is a control-like character but is accepted as a valid character in passwords. The test vectors show an example where a password written in mathematical alphanumeric symbols is normalized via NFKD and encoded into a byte sequence based on plain Latin characters.
Deriving the decryption key (kdf module)
The decryption key is an intermediate key derived from the password, used both for password verification and for the final decryption of the secret. It is derived according to the function and params of the kdf module, and two KDFs are defined. PBKDF2-SHA-256 has function "pbkdf2" and takes params c, dklen, prf ("hmac-sha256"), and salt (RFC 2898). scrypt has function "scrypt" and takes params dklen, n, p, r, and salt (RFC 7914). In both cases, message is empty.
Password verification (checksum module)
Whether the password is correct is checked using checksum.message, cipher.message, and the kdf together. The procedure is to take the second 16-byte segment of the decryption key (decryption_key[16:32]), append cipher.message to it, compute the SHA256 hash, and compare it against checksum.message. If they match, the password is valid. Only SHA-256 (function "sha256") is defined as the checksum hash function, and the params and message inputs are empty. A key difference from existing standards is the use of SHA-256 instead of Keccak256.
Secret decryption (cipher module)
Since the secret is encrypted using the decryption key via cipher.function, decrypting it also uses the decryption key together with cipher.function and cipher.params. If the decryption key is longer than the key length required by the cipher algorithm, it is truncated to the correct number of bits. The only cipher defined is AES-128 in counter mode (function "aes-128-ctr", RFC 3686), which takes iv as a param. For aes-128-ctr, only the first 16 bytes of the decryption key are used as the AES key.
Metadata fields and version
description is an optional field describing the purpose of the keystore; it helps with identification but is not necessarily unique. pubkey is the public key corresponding to the stored private key, which improves user experience and security by allowing the public key to be obtained without entering the password, and is required if the secret is a private key. path is a string indicating the position in the key tree as defined by ERC-2334; an empty string "" is used if the path is unknown or not applicable. uuid is a randomly generated UUID per RFC 4122, a 128-bit identifier pointing to a specific set of keys or account. version is fixed at 4, a value chosen to avoid conflicts with existing Ethereum keystore standards. The fields required by the JSON schema are crypto, path, uuid, and version.
Compatibility with existing keystores
Because this spec does not support the Keccak256 checksum, it is not backward compatible with the existing Ethereum Web3 Secret Storage definition. While the checksum module's structure technically allows a Keccak checksum to be included, doing so would undermine this standard's goal of remaining neutral with respect to other projects, so it is not included. This proposal also assumes ERC-2333 (BLS key generation) and ERC-2334 (key paths).
Proposal status
This proposal is still in Review status. Since its content may change during the review process, it is best to check the latest version of the original when implementing it.