Proof-of-Work-Enabled Relay (PoWER) is a client puzzle protocol meant to mitigate denial-of-service (DoS) attacks against Monero nodes caused by spam transactions. Monero nodes provide challenges that require clients/peers to provide a solution in order to relay high-input transactions.
This document contains instructions on how to follow the protocol.
Currently, verification of FCMP++ transactions with many inputs (e.g. 128-input transactions) can take several seconds on high-end hardware, while creation of invalid transactions is almost instantaneous. An attacker can exploit this asymmetry by spamming nodes with invalid transactions.
PoWER adds a computational cost by requiring Proof-of-Work (PoW) to be performed to enable relaying of high-input transactions.
| Parameter | Value | Description |
|---|---|---|
INPUT_THRESHOLD |
8 | PoWER is required for transactions with input counts greater than this. Transaction with input counts less than or equal to this value can skip PoWER. |
HEIGHT_WINDOW |
2 | Amount of block hashes that are valid as input for RPC PoWER challenge construction. |
DIFFICULTY |
200 | Fixed value used for difficulty calculation. |
PERSONALIZATION_STRING |
"Monero PoWER" | Personalization string used in PoWER related functions. |
- Concatenation of bytes is denoted by
||. - All operations converting between integers and bytes are in little endian encoding.
Equi-X is the PoW algorithm used for PoWER challenges and solutions.
Equi-X is a CPU-friendly client-puzzle that takes in a "challenge" (bytes) and outputs a 16-byte array "solution".
Challenges are constructed differently depending on the interface. The below sections explain each interface.
For RPC (and ZMQ-RPC):
challenge = (PERSONALIZATION_STRING || tx_prefix_hash || recent_block_hash || nonce)
where:
PERSONALIZATION_STRINGis the string "Monero PoWER" as bytes.tx_prefix_hashis the transaction prefix hash of the transaction being relayed.recent_block_hashis a hash of a block within the lastHEIGHT_WINDOWblocks.nonceis a 32-bit unsigned integer.
In the Monero codebase, this is the create_challenge_rpc function.
RPC endpoints that relay transactions contain fields where this data must be passed alongside the transaction.
Note that these fields are not required when any of the following are true:
- The transaction has less than or equal to
INPUT_THRESHOLDinputs. - The transaction orignates from a local/trusted source (unrestricted RPC, localhost, etc)
For P2P:
challenge = (PERSONALIZATION_STRING || seed || difficulty || nonce)
where:
PERSONALIZATION_STRINGis the string "Monero PoWER" as bytes.seedis a random 128-bit unsigned integer generated for each connection.difficultyis the 32-bit unsigned integer difficulty parameter the node requires to be used.nonceis a 32-bit unsigned integer.
In the Monero codebase, this is the create_challenge_p2p function.
seed and difficulty are provided by nodes in the initial P2P handshake message. Note that in the Monero codebase, seed is split into two 64-bit unsigned integers representing the low and high bits.
nonce should be adjusted until a valid Equi-X solution is produced that passes the difficulty formula with difficulty, then a NOTIFY_POWER_SOLUTION message should be sent containing the solution and nonce. This will enable high input transaction relay for that connection.
A PoWER solution has 2 requirements:
- It must be a valid Equi-X solution.
- It must pass a difficulty formula.
The nonce in challenges should be adjusted until both 1 and 2 are satisfied.
For 1, create an Equi-X solution for the challenge data created previously.
Note that equix_solve does not always create valid solutions. The challenge for
all interfaces contain a nonce field that should be adjusted until equix_solve
produces valid solution(s).
For 2, a difficulty scalar must be created with:
scalar = to_le_bytes(blake2b_32(PERSONALIZATION_STRING || challenge || solution))
where:
to_le_bytesconverts a 4-byte array into a 32-bit unsigned integer in little endian order.blake2b_32is ablake2bhash set to a 32-bit output.PERSONALIZATION_STRINGis the string "Monero PoWER".challengeare the full challenge bytes.solutionare the Equi-X solution bytes.
In the Monero codebase, this is the create_difficulty_scalar function.
scalar must now pass the following difficulty formula:
scalar * difficulty <= MAX_UINT32
where:
difficultyis either a constant (DIFFICULTY) for RPC, or thedifficultyreceived from a peer for P2P.
In the Monero codebase, this is the check_difficulty function.