Skip to content
Open

PoWER #230

Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
712f53b
cleanup signatures and start tests
hinto-janai Nov 20, 2025
086d2bb
solve_rpc test
hinto-janai Nov 24, 2025
834a3da
port tests + apply fixes
hinto-janai Nov 27, 2025
1d91f61
checkout rpc changes
hinto-janai Nov 27, 2025
5014d67
rpc: fixes
hinto-janai Nov 27, 2025
fa62a4c
checkout p2p
hinto-janai Nov 27, 2025
64cbd2f
p2p: fixes
hinto-janai Nov 28, 2025
dd93329
checkout wallet2
hinto-janai Nov 28, 2025
22e4357
revert external
hinto-janai Nov 28, 2025
261065a
Merge remote-tracking branch 'seraphis/fcmp++-alpha-stressnet' into p…
hinto-janai Jan 20, 2026
6ebc21e
apply rpc/zmq diffs
hinto-janai Jan 20, 2026
4dc12ac
apply docs diff
hinto-janai Jan 20, 2026
a71093d
wallet: check input.size()
hinto-janai Jan 20, 2026
309dc74
common: update power docs
hinto-janai Jan 20, 2026
202eb32
challenge_nonce -> seed, fix p2p challenge construction and docs
hinto-janai Jan 20, 2026
8a4116d
tests: update p2p challenge data
hinto-janai Jan 20, 2026
258079a
rpc: revert
hinto-janai Jan 22, 2026
4880892
wallet: finish commit_tx
hinto-janai Jan 22, 2026
1d0847f
small fixes
hinto-janai Jan 23, 2026
6ef6e01
signature, doc fixes
hinto-janai Jan 26, 2026
621dacf
update diff numbers
hinto-janai Jan 27, 2026
19918f5
complete p2p power handshake
hinto-janai Jan 28, 2026
c95acc5
clean includes
hinto-janai Jan 28, 2026
7922399
fix tests
hinto-janai Jan 29, 2026
b6f94ab
fixes, enable power for outgoing peer
hinto-janai Jan 29, 2026
6737d6a
small fixes, add typedefs for arrays
hinto-janai Jan 29, 2026
d305b5b
wallet+zmq fixes
hinto-janai Jan 29, 2026
cdb3fae
zmq: update macro type
hinto-janai Jan 29, 2026
c270780
Merge branch 'fcmp++-alpha-stressnet' into power and fix conflicts
hinto-janai Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@
path = external/mx25519
url = https://github.com/jeffro256/mx25519
branch = unclamped
[submodule "external/equix"]
path = external/equix
url = https://github.com/tevador/equix
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ if(NOT MANUAL_SUBMODULES)
check_submodule(external/randomx)
check_submodule(external/supercop)
check_submodule(external/mx25519)
check_submodule(external/equix)
endif()
endif()

Expand Down Expand Up @@ -453,7 +454,7 @@ elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*")
set(BSDI TRUE)
endif()

include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include external/mx25519/include)
include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include external/mx25519/include external/equix/include)

if(MINGW)
set(DEFAULT_STATIC true)
Expand Down
134 changes: 134 additions & 0 deletions docs/POWER.md
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main document describing what PoWER is and how to follow it.

Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# PoWER

Proof-of-Work-Enabled Relay (PoWER) is a [client puzzle protocol](https://en.wikipedia.org/wiki/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.

- [Background](#background)
- [Definitions and notes](#definitions-and-notes)
- [Calculating PoWER challenges and solutions](#calculating-power-challenges-solutions)
- [Challenge](#challenge)
- [RPC](#rpc)
- [P2P](#p2p)
- [Solution](#solution)
- [Equi-X](#equi-x)
- [Difficulty](#difficulty)

## Background

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.

## Definitions and notes

| 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.

## Calculating PoWER challenges and solutions

[Equi-X](https://github.com/tevador/equix) is the PoW algorithm used for PoWER challenges and solutions.

Equi-X is a CPU-friendly client-puzzle that takes in a ["challenge" (bytes)](https://github.com/tevador/equix/blob/c0b0d2bd210b870b3077f487a3705dfa7578208f/include/equix.h#L121) and outputs a [16-byte array "solution"](https://github.com/tevador/equix/blob/c0b0d2bd210b870b3077f487a3705dfa7578208f/include/equix.h#L28-L30).

### Challenge

Challenges are constructed differently depending on the interface. The below sections explain each interface.

#### RPC

For RPC (and ZMQ-RPC):

```
challenge = (PERSONALIZATION_STRING || tx_prefix_hash || recent_block_hash || nonce)
```

where:

- `PERSONALIZATION_STRING` is the string "Monero PoWER" as bytes.
- `tx_prefix_hash` is the transaction prefix hash of the transaction being relayed.
- `recent_block_hash` is a hash of a block within the last `HEIGHT_WINDOW` blocks.
- `nonce` is 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_THRESHOLD` inputs.
- The transaction orignates from a local/trusted source (unrestricted RPC, localhost, etc)

#### P2P

For P2P:

```
challenge = (PERSONALIZATION_STRING || seed || difficulty || nonce)
```

where:

- `PERSONALIZATION_STRING` is the string "Monero PoWER" as bytes.
- `seed` is a random 128-bit unsigned integer generated for each connection.
- `difficulty` is the 32-bit unsigned integer difficulty parameter the node requires to be used.
- `nonce` is 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.

## Solution

A PoWER solution has 2 requirements:

1. It must be a valid Equi-X solution.
2. It must pass a difficulty formula.

The `nonce` in challenges should be adjusted until both 1 and 2 are satisfied.

### Equi-X

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).

### Difficulty

For 2, a difficulty scalar must be created with:

```
scalar = to_le_bytes(blake2b_32(PERSONALIZATION_STRING || challenge || solution))
```

where:

- `to_le_bytes` converts a 4-byte array into a 32-bit unsigned integer in little endian order.
- `blake2b_32` is a `blake2b` hash set to a 32-bit output.
- `PERSONALIZATION_STRING` is the string "Monero PoWER".
- `challenge` are the full challenge bytes.
- `solution` are 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:

- `difficulty` is either a constant (`DIFFICULTY`) for RPC, or the `difficulty` received from a peer for P2P.

In the Monero codebase, this is the `check_difficulty` function.
1 change: 1 addition & 0 deletions external/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ add_subdirectory(easylogging++)
add_subdirectory(qrcodegen)
add_subdirectory(randomx EXCLUDE_FROM_ALL)
add_subdirectory(mx25519)
add_subdirectory(equix)
1 change: 1 addition & 0 deletions external/equix
Submodule equix added at c0b0d2
2 changes: 2 additions & 0 deletions src/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ set(common_sources
password.cpp
perf_timer.cpp
pruning.cpp
power.cpp
spawn.cpp
threadpool.cpp
updates.cpp
Expand Down Expand Up @@ -75,6 +76,7 @@ target_link_libraries(common
${Boost_REGEX_LIBRARY}
${Boost_CHRONO_LIBRARY}
PRIVATE
equix_static
${OPENSSL_LIBRARIES}
${EXTRA_LIBRARIES})
target_include_directories(common
Expand Down
Loading