---
# System prepended metadata

title: 'QPoW Mining: Workflow and Algorithms'

---

# QPoW Mining: Workflow and Algorithms

This document explains the Quantum Proof of Work (QPoW) design used by the Quantus Network, focusing on the end-to-end mining workflow and the algorithms that define validity, difficulty adjustment, and chain selection.

Key components (by crate/directory):
- Runtime pallet: pallets/qpow
- Math/verification: qpow-math
- Client (mining worker, block import, chain selection): client/consensus/qpow
- Node service and miner orchestration: node
- Mining rewards: pallets/mining-rewards
- External miner protocol: EXTERNAL_MINER_PROTOCOL.md and miner-api

Terms used throughout:
- mining hash: the block header hash being mined, i.e., the header pre-hash (no seal)
- seal: the 64-byte nonce placed in the header’s digest as the PoW seal
- distance threshold (aka threshold): U512 target bound for acceptance
- difficulty: derived inversely from threshold
- work: cumulative chain work used in fork choice


## 1) High-level mining flow

1. Proposing a block:
   - The node builds a block proposal from the best chain.
   - A pre-runtime digest is optionally inserted that encodes the miner rewards account (for rewards attribution).
   - The proposal header hash (pre-hash) becomes the mining hash.

2. Mining metadata and loop:
   - The mining worker produces metadata: { best_hash, pre_hash, difficulty } and exposes it to a miner loop (local or external).
   - The miner searches for a 64-byte nonce such that the QPoW validity condition holds under the current threshold (from runtime).

3. Finding a solution:
   - For each nonce candidate, compute the distance with respect to the mining hash (see Section 2) and check if distance <= threshold.
   - If a solution is found, submit the 64-byte nonce as a seal.

4. Import and verification:
   - On submission, the node imports the block, extracting the seal from the header digest and passing it to the runtime to verify via the QPoW runtime API.
   - The runtime recomputes the target/distance and accepts or rejects the block accordingly.

5. Difficulty and rewards:
   - OnFinalize, the runtime updates difficulty (via threshold adjustment) using observed block time EMA.
   - The mining-rewards pallet extracts the miner from the pre-runtime digest and mints rewards and fees accordingly.
   - Chain finalization proceeds at a bounded reorg depth.


## 2) QPoW proof definition

QPoW defines validity based on a deterministic RSA-like group construction derived from the mining hash and a Poseidon2 permutation to map group elements to a 512-bit field. The proof search is a hunt for a 64-byte nonce that minimizes the XOR distance between a fixed target element and the nonce-dependent element.

Given:
- h = mining hash (pre-hash) as U512 derived from [u8; 32]
- s = nonce as U512 derived from [u8; 64]
- (m, n) = group parameters derived deterministically from h (see 2.1)
- Poseidon2-512: a cryptographic permutation producing 512-bit outputs

Definitions:
- Base exponentiation (no hash): g(h, s) = m^(h + s) mod n
- Poseidon mapping: G(h, s) = Poseidon2_512(g(h, s))
- Target element: T = G(h, 0)
- Nonce element: X(s) = G(h, s)
- Distance: D(s) = T XOR X(s) as U512

Validity condition:
- A nonce s is valid if D(s) <= threshold, where threshold is dynamic and maintained by the runtime pallet.

Properties:
- D(s) is symmetric XOR distance; lower is better.
- threshold is a U512 bound; smaller threshold implies higher difficulty.


### 2.1) Deriving RSA-like parameters (m, n)

To define the group element computation, QPoW derives a pair (m, n) from the mining hash using Poseidon2:

- m is a 256-bit integer: m = U512(Poseidon2_256(h_bytes))
- n is a 512-bit integer initialized as n0 = U512(Poseidon2_512(m_bytes)) and then rerolled via Poseidon if constraints fail.

Constraints for n:
- n must be odd,
- n > m,
- gcd(m, n) = 1 (coprime),
- n must be composite (not prime).

Reroll step:
- While any constraint fails, set n := U512(Poseidon2_512(n_bytes)) and re-check.

Notes:
- is_coprime uses Euclidean GCD.
- is_prime uses a Miller–Rabin test with 32 deterministic bases derived from Poseidon2, giving a negligible false positive rate (~2^-64).


### 2.2) Mapping to group and distance

- Compute g(h, s) = m^(h + s) mod n.
- Map via Poseidon2-512: G(h, s) = Poseidon2_512(to_bytes(g(h, s))).
- Define T = G(h, 0) (fixed per block) and X(s) = G(h, s).
- The proof’s score is the XOR distance D(s) = T XOR X(s).
- Accept if D(s) <= threshold.


## 3) Mining loop and optimizations

Brute-force mining iterates over nonces s and checks D(s) <= threshold. QPoW includes an optimization for scanning contiguous nonce ranges efficiently:

Incremental exponentiation:
- Let v0 = m^(h + s0) mod n for a starting nonce s0.
- For each next nonce s = s_prev + 1, we can update:
  v_next = (v_prev * m) mod n
- This avoids recomputing the full exponentiation for each s.
- At each step, compute X(s) = Poseidon2_512(v) and check distance against the threshold.

Practical loop:
- The node mines nonces in small batches (e.g., 300) using this incremental method.
- If no solution is found, it advances the starting nonce and repeats.


## 4) Difficulty and threshold adjustment

The runtime maintains a dynamic distance threshold and derives difficulty from it.

Key storage variables:
- CurrentDistanceThreshold: U512
- TotalWork: U512 (cumulative)
- BlockTimeEma: u64 (ms)
- LastBlockTime, LastBlockDuration: u64 (ms)

Parameters (runtime constants):
- InitialDistanceThresholdExponent: u32
- DifficultyAdjustPercentClamp: u8 (percent)
- TargetBlockTime: u64 (ms)
- EmaAlpha: u32 (0..1000; 1000 => 1.0)
- FixedU128Scale: u128 (default 10^18)
- MaxReorgDepth: u32
- MaxDistanceMultiplier: u32 (multiplier for max distance)

Initialization:
- initial_threshold = 1 << InitialDistanceThresholdExponent
- max_distance = initial_threshold << MaxDistanceMultiplier
- min_distance = 1

Difficulty:
- difficulty = max_distance / CurrentDistanceThreshold
- This grows when threshold shrinks, and vice versa.

Threshold adjustment (per block, on_finalize):
1) Measure block_time = now - LastBlockTime (ms) and update BlockTimeEma using EmaAlpha (scaled by 1000).
2) Compute ratio = clamp(observed/target, 1 ± clamp%), where:
   - observed = BlockTimeEma
   - target = TargetBlockTime
   - clamp% = DifficultyAdjustPercentClamp
3) Adjust threshold:
   - Represent ratio as FixedU128; then:
     new_threshold ≈ (CurrentDistanceThreshold / FixedU128Scale) * ratio_fixed
   - Clamp into [min_distance, max_distance]
4) Update CurrentDistanceThreshold = new_threshold
5) Update TotalWork += difficulty
6) Reset counters for next iteration; record timestamps

Notes:
- The division by FixedU128Scale first avoids overflow before scaling by ratio.
- Threshold is never allowed to drop below 1 or exceed max_distance.


## 5) Verification and seals

Seal structure:
- A seal is exactly 64 bytes (U512) representing the nonce.

Import path:
- The node extracts the seal from the header digest (it must carry the correct consensus engine ID).
- The node passes (parent_hash, pre_hash, nonce) to the runtime QPoW API.
- The runtime recomputes (m, n), T, X(s), distance D(s), and validates D(s) <= threshold (fetched from runtime state).
- If validation fails or the seal length is not 64 bytes, the block is rejected.

Important: The PoW engine ID in header digests uses sp_consensus_pow::POW_ENGINE_ID (b"pow_") for both the pre-runtime digest and the seal item. The QPoW runtime API is exposed via sp_consensus_qpow::QPoWApi, but the digest engine ID used in headers remains the POW_ENGINE_ID constant.


## 6) Fork-choice (HeaviestChain) and finalization

HeaviestChain:
- The client selects the best chain based on total cumulative work reported by the runtime (QPoW: TotalWork).
- Among leaves:
  - Choose the chain with the highest total work.
  - If equal work, tie-break by greater height.
  - Enforce a maximum reorg depth: if switching to a higher-work chain exceeds MaxReorgDepth, that chain is marked “ignored”.
- Ignored chains:
  - The client persists a small flag for chain heads that exceed reorg limits to prevent oscillation.

Finalization:
- Periodically finalize blocks older than MaxReorgDepth - 1 behind the current best.
- This provides stable finality under bounded reorg assumptions.


## 7) Mining rewards

The mining-rewards pallet:
- OnFinalize, extracts the miner account from the pre-runtime digest (under POW_ENGINE_ID). The digest payload is the SCALE-encoded AccountId.
- Mints:
  - Miner block reward (configurable constant)
  - Treasury block reward (configurable constant)
  - Transaction fees collected during the block (credited to the miner)
- Stores and emits transfer proofs (qp_wormhole::TransferProofs) as needed by the chain’s token model.

If no miner pre-runtime digest is present or decode fails:
- Rewards are minted to the Treasury account.


## 8) External miner support

The node can offload mining to an external service over HTTP.

Protocol (see EXTERNAL_MINER_PROTOCOL.md, miner-api):
- POST /mine
  - Provides: job_id, mining_hash (pre-hash), distance_threshold, nonce_start, nonce_end
  - Accepted => miner starts scanning the nonce range
- GET /result/{job_id}
  - Returns status; on completion contains winning work (64-byte nonce hex)
- POST /cancel/{job_id}
  - Cancels a job (used when mining metadata changes or node shuts down)

Node behavior:
- When mining metadata changes (e.g., new best block), the node cancels any stale job and submits a fresh job to the external miner.
- On solution, the node re-checks that the mining metadata version is still current and submits the seal.


## 9) Edge cases, guards, and safety notes

- Zero nonce guard: nonce == 0 is disallowed during validation and considered invalid (treated as a programming error during verify paths).
- Seal length: Exactly 64 bytes. Any other length results in rejection.
- Determinism: (m, n) generation is fully deterministic from the mining hash; miners and validators derive identical parameters.
- Modular arithmetic:
  - Modular exponentiation uses big-integer arithmetic; the incremental update path ensures correctness via tested equivalence against full mod_pow for s, s+1, ...
- Primality checking:
  - Miller–Rabin with deterministic Poseidon-derived bases increases robustness of composite n selection.
- Bounded difficulty changes:
  - The clamp (±DifficultyAdjustPercentClamp%) limits threshold adjustments per block.
- Bounded distance threshold:
  - Threshold is clamped to [1, max_distance] to prevent degenerate states.
- Reorg safety:
  - MaxReorgDepth bounds reorgs; chains that would require deeper reorgs are ignored.
- Rewards attribution:
  - Rewards rely on the miner’s AccountId embedded into a pre-runtime header digest (POW_ENGINE_ID). If missing/invalid, rewards go to treasury.


## 10) Putting it together: miner pseudocode

This is a straightforward, local miner loop using the incremental exponentiation optimization:

1) Get mining metadata from the node:
   - h = pre_hash (32 bytes)
   - threshold = runtime.get_distance_threshold()
   - best_hash = chain tip (for staleness checks)

2) Derive group parameters:
   - (m, n) = get_random_rsa(h)

3) Precompute:
   - T = Poseidon2_512( m^(h + 0) mod n )

4) Initialize a range [s, s + k):
   - v = m^(h + s) mod n

5) For i in 0..k:
   - nonce = s + i
   - X = Poseidon2_512(v)
   - D = T XOR X
   - If D <= threshold => return nonce
   - v = (v * m) mod n

6) If not found:
   - s += k and repeat (or request a new job if metadata changed)

7) On success:
   - Submit the 64-byte big-endian encoding of nonce as the seal.


## 11) Runtime API surface (consumer view)

The node/client interacts with the runtime via sp_consensus_qpow::QPoWApi to:
- verify_nonce_on_import_block(pre_hash, nonce)
- verify_nonce_local_mining(pre_hash, nonce)  // local check variant
- get_distance_threshold()
- get_difficulty()
- get_max_distance()
- get_total_work()
- get_block_time_ema()
- get_last_block_time()
- get_last_block_duration()
- get_max_reorg_depth()
- get_chain_height()
- get_random_rsa(h)
- hash_to_group_bigint(h, m, n, s)

Note: Names reflect the runtime implementation; precise signatures include concrete types (e.g., [u8; 32]/[u8; 64]/U512).


## 12) Configuration checklist

- TargetBlockTime (ms): desired average block time.
- EmaAlpha (0..1000): smoothing factor for block time EMA.
- DifficultyAdjustPercentClamp (%): maximum per-block threshold adjustment.
- InitialDistanceThresholdExponent: sets the initial threshold magnitude.
- MaxDistanceMultiplier: caps threshold to protect from unbounded growth.
- MaxReorgDepth: bounds fork switch depth.
- Miner and Treasury rewards per block (mining-rewards pallet).
- Rewards address: ensure a valid AccountId is embedded via pre-runtime digest when authoring.


## 13) Practical notes

- Local mining batch size:
  - The default local miner scans small batches (e.g., 300 nonces) to remain responsive to new metadata and to cooperate with other node tasks.
- Logging and observability:
  - Enable `sc_consensus_pow=trace`, `qpow=debug`, `math=debug` to inspect mining math, threshold adjustments, and verification.
- Deterministic end-to-end:
  - Miners, validators, and full nodes perform identical math given (h, s). Disagreements imply malformed seals or non-canonical/malformed headers.


## 14) Glossary

- Mining hash / pre-hash: The block header hash without the PoW seal.
- Seal: The 64-byte nonce placed in the header digest under the PoW engine ID.
- Threshold: The U512 bound that the XOR distance must not exceed for a valid solution.
- Difficulty: Inverse of threshold relative to a fixed max_distance, difficulty = max_distance / threshold.
- Total work: Cumulative sum of per-block difficulty, used by fork-choice.
- Reorg depth: Number of blocks that would be replaced if switching to another chain head.
- Poseidon2: The permutation used to map group elements to 512-bit values for distance calculation.


---

By following this specification, a miner (local or external) can interoperate with the Quantus Network’s QPoW consensus, produce valid seals, and submit mined blocks that the runtime will accept and reward.