Introduce Legacy module

Hello everyone,

Today I would like to propose a LIP for the roadmap objective “Define state model and state root”. This LIP specifies the legacy module. The main roles of this module are:

  • maintaining all non-reclaimed unregistered legacy balances,
  • implementing a command to transfer a legacy balance to a user account (reclaim),
  • implementing a command to add a BLS key to a validator account (register BLS key).

Looking forward to your feedback!

Here is the LIP draft:

Title: Introduce Legacy module
Author: Andreas Kendziorra <>
        Maxime Gagnebin <>
        Rishi Mittal <>
Status: Draft
Type: Standards Track
Requires: 0018


The Legacy module maintains all accounts on the Lisk mainchain that received balance transfers to their address in the old 8-byte format and for which no public key is associated. The Legacy module also implements a command allowing delegates without a BLS key to register one.

In this LIP, we specify the properties of the Legacy module, along with their serialization and default values. Furthermore, we specify the commands and the functions that can be called from off-chain services.

This module is only needed for the Lisk mainchain.


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


Once LIP 0018 is active on the Lisk mainchain, all nodes for the Lisk mainchain must maintain the accounts that received some funds before the implementation of LIP 0018, but do not have an associated public key. The balance of these accounts is maintained in the legacy accounts substore and can be recovered with a reclaim transaction.

Furthermore, delegates registered before the implementation of the Validators module do not have a registered BLS key. This module implements a command to allow those delegates to register a BLS key and hence participate in the certification generation process.

Implementing the Legacy module avoids the need for other modules (specifically the Token module and the Validators module) to handle legacy behaviors present only on the Lisk mainchain. This module is only part of the Lisk mainchain, and should not be implemented in any sidechain.


Module ID

The Legacy module has the module ID MODULE_ID_LEGACY (defined in the table below).

Notation and Constants

We define the following constants:

Name Type Value Description
MODULE_ID_LEGACY uint32 TBD Module ID of the Legacy module.
COMMAND_ID_RECLAIM uint32 1000 Command ID of the reclaim command.
COMMAND_ID_REGISTER_BLS_KEY uint32 1 Command ID of the register BLS key command.
STORE_PREFIX_LEGACY_ACCOUNTS bytes 0x0000 Store prefix of the legacy accounts substore. This contains the addresses and balances of legacy accounts.

Functions from Other Modules

Calling a function fct from another module (named moduleName) is represented by moduleName.fct(required inputs).

Legacy Module Store

Legacy Accounts Substore

This substore contains an array with the addresses and the balances of all legacy accounts for which no reclaim transaction was included.

Store Prefix, Store Key, and Store Value
  • The store prefix is set to STORE_PREFIX_LEGACY_ACCOUNTS.
  • The store key is an 8-byte value representing the legacy address.
  • The store value is set to the serialization using legacyAccountsSchema of the balance of the legacy account.
JSON Schema
legacyAccountsSchema = {
    "type": "object",
    "required": ["balance"],
    "properties": {
        "balance": {
            "dataType": "uint64",
            "fieldNumber": 1
Properties and Default values

This substore contains an entry for each legacy address for which no reclaim transaction was included. See also the “Accounts without Public Key” section in LIP 0018.

The module store will be initialized with these legacy addresses and their respective balances during the execution of the genesis block.

Internal Functions

Legacy Addresses

Obtaining the legacy 8-byte address from a public key.


A 32-byte value representing a public key.


The 8-byte legacy address corresponding to the given input.

    hashedKey = SHA-256(publicKey)
    firstEightBytes = first 8 bytes of hashedKey
    reversedEightBytes = firstEightBytes reversed
    return reversedEightBytes



This command allows users to reclaim tokens from a legacy account as defined in LIP 0018. Here, we clarify the verification and execution logic with respect to the module store.

Transactions executing this command have:

  • moduleID = MODULE_ID_LEGACY,

The params property of a reclaim transaction must obey the following schema:

reclaimParamsSchema = {
    "type": "object",
    "required": ["amount"],
    "properties": {
        "amount": {
            "dataType": "uint64",
            "fieldNumber": 1

For a reclaim transaction trs to be valid, the legacy accounts substore must contain an entry with store key equal to getLegacyAddress(trs.senderPublicKey) and store value {"balance": trs.params.amount}, serialized using legacyAccountsSchema.


When a reclaim transaction trs is executed, the following is done:

  • Delete the entry from the legacy accounts substore with store key getLegacyAddress(trs.senderPublicKey).
  • Call the function mint(newAddress, 0, trs.params.amount), where newAddress is the 20-byte address derived from trs.senderPublicKey.
    The function mint is defined in the Token module.

Register BLS Key

This command allows migrated legacy validators without a BLS key to register one. This command cannot be used to modify an existing BLS key, as this is forbidden by the Validators module. Transaction executing this command have:

  • moduleID = MODULE_ID_LEGACY,

The params property of a register BLS key transaction must obey the following schema:

registerBLSKeyParamsSchema = {
    "type": "object",
    "required": ["blsKey", "proofOfPossession"],
    "properties": {
        "blsKey": {
            "dataType": "bytes",
            "fieldNumber": 1
        "proofOfPossession": {
            "dataType": "bytes",
            "fieldNumber": 2

Executing a transaction trs triggering an register BLS key command is done by calling the function setValidatorBLSKey(validatorAddress, trs.params.proofOfPossession, trs.params.blsKey) where validatorAddress is the 20-byte address derived from trs.senderPublicKey and setValidatorBLSKey is defined in the Validators module. If this function returns false, the transaction is invalid.

Protocol Logic for Other Modules

This module does not expose any functions.

Endpoints for Off-Chain Services


This function provides the legacy address and balance of the corresponding legacy accounts.

  • inputParameter1: publicKey, an Ed25519 public key

Either an object with the properties legacyAddress and balance, where legacyAddress is the legacy address for publicKey and balance is the balance of the corresponding legacy account, or undefined.

    let legacyAddress be the legacy address of publicKey
    if there exists no entry in the legacy accounts substore with store key equal to legacyAddress:
        return undefined
        let balance be the value of the balance property of the legacy accounts substore entry with store key legacyAddress
        return {"legacyAddress": legacyAddress,
                "balance": balance}

Backwards Compatibility

This LIP defines a new module and specifies its store, which in turn will become part of the state tree and will be authenticated by the state root. As such, it will induce a hardfork.

Reference Implementation



I just created the pull request for this proposal on GitHub: Add LIP "Introduce Legacy module" by MaximeGagnebin · Pull Request #103 · LiskHQ/lips · GitHub</title

The PR for this LIP has been merged and the LIP is now drafted: