QRL Improvement Proposal (QIP-012)
Implementing transaction replay attack protection
Overview & status
|Author||Peter Waterland (@sug0r)|
Whilst currently an extremely low risk vulnerability, replay attack protection should ideally be considered and implemented in the next hard fork to completely mitigate this attack vector.
Typically a replay attack involves a transaction from one instance of a blockchain network being maliciously retransmitted by an attacker onto another separate network (generally a persisting network fork). With a simple transaction sending funds from Alice to Bob on chain A, a replay attack of the transaction would also move funds from Alice to Bob’s account on chain B (without permission of Alice).
This is currently a potential risk between QRL mainnet and the public QRL testnet. However, it is not recommended to use the same QRL XMSS account on both testnet and mainnet due to the potential risk of OTS re-use. Furthermore, testnet balances are worthless and are not the same as the ledger balances on mainnet meaning there is no value in attempting a replay attack between QRL mainnet and testnet.
QRL will fork from proof-of-work to proof-of-stake in the future (and it is possible other forks of QRL mainnet may be created) with the risk of both sides of the fork persisting and being potentially susceptible to replay attack.
To mitigate a replay attack it is necessary to somehow associate a transaction with a specific instance of the network such that it will not be found to be valid on the forked network. This requires the addition of some network or chain specific data to be signed into the transaction.
network ID - A unique numerical identifier, nodes on each network would find any transaction not using the correct identifier invalid. An example would be
'0000' = devnet,
'0001' = testnet,
'0002' = mainnet,
'0003' = mainnet pos, or simply stamping the genesis block hash into each transaction. Note this does not prevent replay attacks in later forks of a network using the same identifier.
block hash - each block in the chain has a unique hash value obtained from aggregating and hashing the block header (and incorporating the previous block header hash). Thus, stamping each transaction with the a recent 32 byte block header hash is a simple way of preventing a replay attack. The significant downside is this requires the transaction signer to know something about the current QRL chain which could be a problem for offline transaction signing. Another consideration is that choosing the most recent block could lead to a transaction entering the mempool only for the block to be discarded in a mini-fork and then the transaction become invalid. It would make sense then to choose a block hash at a given depth from the tip of the chain or specific epoch blocks at 1k intervals (or something suitable).
tx hash - it is possible for each transaction to optionally include the txhash of the last transaction from the account. It would therefore force an attacker to replay all historical transactions since the fork to achieve success - if there are no transactions since the fork then this would not be effective at preventing a replay attack.
I would suggest adding a network ID to the transaction protocol at the next major release. This network ID would actually not need to be included in the transaction payload and so would not grow the transaction size. Pseudocode to demonstrate this functionality would be:
After each subsequent hard fork the network ID would be updated to make transactions continually incompatible between hard forked networks..
Consensus changes required
Any changes to the base transaction format and construction are not backwards compatible with existing versions of qrl-node and this is therefore a hard fork upgrade to be introduced at the OCAML fork later this year.
Thanks / Attribution
Although this subject has previously been discussed at length by the core development team, I would like to thank Red4SEC for reminding us of this future attack vector.