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

Latest commit

 

History

History
157 lines (101 loc) · 11.5 KB

lip-0037.md

File metadata and controls

157 lines (101 loc) · 11.5 KB
LIP: 0037
Title: Use message tags and chain identifiers for signatures
Author: Andreas Kendziorra <[email protected]>
Discussions-To: https://research.lisk.com/t/use-message-tags-and-chain-identifiers-for-signatures/280
Status: Active
Type: Standards Track
Created: 2021-04-07
Updated: 2024-01-04
Replaces: 0009

Abstract

We introduce the concept of message tags for signatures. These message tags are prepended to the binary messages before being signed. A unique tag has to be used for each message type (transaction, block header, etc.), and in particular for each schema. This ensures that a signature for one message cannot be a valid signature for another message that serializes to the same binary message.

Moreover, we generalize the usage of the chain identifiers (called network identifier in previous LIPs), which are currently used for transaction and block header signatures, to arbitrary signatures. This will prevent replay attacks for arbitrary messages.

Copyright

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

Motivation

We want to avoid (1) replay attacks and (2) the re-usage of a signature for a different message of a different type.

Case (2) could happen if there are two messages that are of different types but are serialized to the same binary message. For example, consider a block header bh that is serialized to the binary message bm. If the binary message bm can be deserialized against a transaction schema to a transaction tx, then a signature of bh would also be a valid signature of tx for the generator public key of bh. Note that for several reasons it is very unlikely that a concrete instance of this example passes block validation for bh and transaction validation for tx. For instance, the current schemas do not allow thatbh.generatorPublicKey and tx.senderPublicKey are encoded in the same position of the binary message.

Nevertheless, it is highly desirable to prevent such re-usage cases in general. In particular for new data structures that require a signature introduced by some future protocol enhancements.

Replay attacks for transactions and blocks, i.e. replaying transactions or blocks on other chains, are already addressed by LIP 0009 and LIP 0024 respectively. However, replay attacks should be prevented for any kind of data structure for the same reason as above.

Rationale

Message Tags

Prepending a tag to a binary message before signing, where the tag is specific to the type of the message, prevents the signature re-usage in general. Consider again the blockheader bh and the transaction tx that are serialized to the same binary message bm. When the tag "LSK_BH_" is used for block header signatures and the tag "LSK_TX_" for transaction signatures, then the signatures for bh and tx are computed by signing the messages "LSK_BH_" + bm and "LSK_TX_" + bm respectively. Hence, the signature of bh cannot be a valid signature for tx and vice versa (assuming no collisions in the signing function).

Note that any updates to the serialization specification of a message type must be accompanied by introducing a new tag. For example, when the transaction schema is updated, a new tag like "LSK_TX:V2_" must be defined and used.

Chain Identifiers

Chain identifiers for transaction signatures and block signatures were already introduced in LIP 0009 and LIP 0024 (as network identifiers) to prevent replay attacks on other chains. Here, we specify how to use a chain identifier for any kind of message to prevent replay attacks on other chains in general. These specifications supersede the specifications given in LIP 0009.

Chain identifiers are 4-byte values that follow a specific format: the first byte is used to identify the network in which the chain is running (either the mainnet, official testnet, or any other test network). The other 3 bytes identify the chain within the network. We include the network-specific prefix explicitly to ensure that a chain does not use the same chain identifier in the test network as in the mainnet.

The first bit of the chain-identifier prefix is always set to 0. Thus, we have a total of 128 available network IDs. This is done to prevent the overflow of the max size of uint32 for hardened paths for Ed25519 keys derivation.

Using 4 bytes instead of 32 bytes as in LIP 0009 has the advantage that users can easily verify that they are signing a transaction for the correct blockchain. Furthermore, the chain identifier can be directly set by the blockchain creator, which is more convenient than generating a random 32-byte value.

Specification

Notation and Constants

Name Type Value Description
CHAIN_ID_LENGTH uint32 4 Length of chain IDs in bytes.
OWN_CHAIN_ID ChainID set in configuration The chain ID of the chain under consideration, see below.

Type Definitions

Name Type Validation Description
ChainID bytes Must be of length CHAIN_ID_LENGTH. ID of a chain.

Message Tags

Every binary message must be tagged before being signed. This is done as specified by the function tagMessage in the pseudo code below: A specific ASCII-encoded tag and a chain identifier are prepended to the message.

def tagMessage(tag: bytes, chainID: bytes, message: bytes) -> bytes:
    return tag + chainID + message

The tag is chosen depending on the message type. Moreover, different serialization methods for the same message type require different tags. The following table defines the tags currently needed.

Name Type Value Description
MESSAGE_TAG_TRANSACTION bytes "LSK_TX_" as ASCII-encoded literal Message tag for signing transaction with serialization specified in LIP "Define New Transaction Schema".
MESSAGE_TAG_BLOCK_HEADER bytes "LSK_BH_" as ASCII-encoded literal Message tag for signing block headers with serialization specified in LIP 0055.
MESSAGE_TAG_MULTISIG_REG bytes "LSK_RMS_" as ASCII-encoded literal Message tag for the signatures contained in the register multisignature command.
MESSAGE_TAG_CHAIN_REG_MESSAGE bytes "LSK_CRM_" as ASCII-encoded literal Message tag for the signatures contained in the chain registration message.
MESSAGE_TAG_POA bytes "LSK_POA_" as ASCII-encoded literal Message tag for the signatures contained in the update authority command.
MESSAGE_TAG_CERTIFICATE bytes "LSK_CE_" as ASCII-encoded literal Message tag for signing certficates with serialization specified in LIP 0061.
MESSAGE_TAG_NON_PROTOCOL_MESSAGE bytes "LSK_NPM_" as ASCII-encoded literal Message tag for signing non-protocol related messages. Used for the Sign Message feature in Lisk Desktop where arbitrary message can be signed and verified.

Creating New Tags

When a new tag is required in the future, the format:

"LSK_" + TAG + "_"

has to be used where:

  • Strings in double quotes are ASCII-encoded literals.
  • TAG is a unique string indicating the scheme and, optionally, additional information, e.g., to specify a version number as shown above. TAG must contain only ASCII characters between 0x21 and 0x7e (inclusive), except that it must not contain underscore (0x5f).

Chain Identifiers

Chain identifiers are 4-byte values. The first byte is set to CHAIN_ID_PREFIX_MAINNET for chains running in the mainnet network and to CHAIN_ID_PREFIX_TESTNET for chains running in the testnet network. The first bit of the chain-identifier prefix is always set to 0. The other 3 bytes must be chosen uniquely for the respective blockchain, i.e., no other blockchain created with the Lisk SDK should use the same 3 bytes.

The following table defines the chain-identifiers prefixes currently specified.

Name Type Value Description
CHAIN_ID_PREFIX_MAINNET bytes 0x00 Chain-identifier prefix for mainnet blockchains.
CHAIN_ID_PREFIX_TESTNET bytes 0x01 Chain-identifier prefix for testnet blockchains.

Own Chain ID

The chain ID of a chain is denoted by the constant OWN_CHAIN_ID which is defined as part of the configuration of the chain. We assume this constant is globally available in the engine and application.

Mainchain Chain ID

For a given network, the chain ID of the mainchain always has the last 3 bytes set to 0. Hence, given any chain ID in a network, the chain ID of the mainchain can be easily obtained by keeping the chain-identifier prefix and setting all other bytes to 0. We define the following utility function to easily obtain the chain ID of the mainchain.

def getMainchainID() -> ChainID:
    return OWN_CHAIN_ID[0:1] +  b'\0'*3

Signing and Verifying with Ed25519

(Note: The following function definitions are superseded by the ones in LIP 0062.)

Let Sign and Verify be the signing and verifying functions of Ed25519 as specified in RFC 8032. Then the signature for a binary message m and a secret key sk is generated by signEd25519(sk, tag, chainID, m) as defined below. tag must be the correct message tag for m and chainID the correct chain identifier for the chain. The resulting signature sig in combination with the message m and the matching public key pk is verified by verifyEd25519(pk, tag, chainID, m, sig).

def signEd25519(sk: bytes, tag: bytes, chainID: bytes, m: bytes) -> bytes:
    taggedMessage = tagMessage(tag, chainID, m)
    return Sign(sk, taggedMessage)
def verifyEd25519(pk: bytes, tag: bytes, chainID: bytes, m: bytes, sig: bytes) -> bool:
    taggedMessage = tagMessage(tag, chainID, msg)
    return Verify(pk, taggedMessage, sig)

Backwards Compatibility

Due to adding message tags to transaction and block signatures, the proposed signature scheme is incompatible with the current one and implies a hard fork.

Reference Implementation

Update generic message signing

Appendix

Examples

TBA