Proof of Work & Difficulty

Work through examples, try the reasoning yourself, then put it into practice.

View Slides

Before you start

  • Read hexadecimal bytes and reason about shifts.
  • Distinguish hash bytes from hexadecimal display text.

What you will practice

  • Decode and canonically encode compact targets with explicit precision loss.
  • Reconstruct all 80 serialized header bytes and double-hash raw bytes.
  • Implement and test inclusive nonce-search boundaries under the course conventions.
In this lesson

Lesson

4 min read

Proof of Work and Compact Targets

Prerequisites: byte order, shifts, hexadecimal, and the hash functions from the foundations module. The goal is to reconstruct the exact bytes being hashed and the integer being compared, then implement a bounded nonce search.

1. State the model before writing code

This pack implements a Bitcoin-inspired teaching format. It double-hashes an 80-byte header, but compares the raw digest as a big-endian integer and decodes all 24 mantissa bits as unsigned. These are the exercise's conventions, not a claim of Bitcoin consensus compatibility. Bitcoin has signed compact-target history and additional validity rules. Consult the Bitcoin block reference when working with actual network data.

A valid header in this exercise satisfies H <= T, where H is the course's hash integer and T is decoded from Bits. Equality passes. Mining repeatedly changes a nonce and tests that same predicate. A failed bounded search says only that this range contained no acceptable candidate.

2. Decode compact targets with integers

Split bits into exponent e = bits >> 24 and mantissa m = bits & 0x00ffffff. For e > 3, shift m left by 8*(e-3) bits. For e <= 3, shift right by 8*(3-e) bits. Right shifts discard low bytes; avoid floating-point powers and fractional targets.

Bits Exponent Mantissa Operation Target
0x04123456 4 0x123456 left 8 0x12345600
0x03123456 3 0x123456 no shift 0x123456
0x02123456 2 0x123456 right 8 0x1234
0x01123456 1 0x123456 right 16 0x12

Use math/big: even a four-byte encoding can describe a target much larger than a machine integer. In this task 0x03800000 decodes to positive 0x800000; the high mantissa bit is data, not a negative sign.

3. Encoding is canonical but lossy

Start with the target's big-endian bytes. If there are at most three bytes, left-pad the mantissa's significance by shifting it to fill three positions. For a longer target retain the three most significant bytes and discard the rest. The byte count becomes the exponent.

If the resulting mantissa's high bit is set, shift it right by one byte and increase the exponent. For example, T = 0x80 starts as mantissa 0x800000 with exponent 1; normalization produces mantissa 0x008000, exponent 2, and bits 0x02008000. Decoding yields 0x80 again. Zero encodes as zero.

Do not assume every target round-trips exactly. 0x12345678 encodes as 0x04123456, which decodes to 0x12345600. The format discards low-order precision. Conversely, multiple noncanonical inputs can decode to the same target; encoding that target returns one canonical representation.

4. Serialize the header before hashing it

The byte layout is fixed:

Byte range Field Encoding
[0,4) Version uint32 little-endian
[4,36) PrevHash supplied 32 bytes, unchanged
[36,68) MerkleRoot supplied 32 bytes, unchanged
[68,72) Timestamp uint32 little-endian
[72,76) Bits uint32 little-endian
[76,80) Nonce uint32 little-endian

With version 2, zero hash fields, timestamp 0x01020304, bits 0x207fffff, and nonce 1, the beginning is 02 00 00 00 and the last twelve bytes are 04 03 02 01 ff ff 7f 20 01 00 00 00. Hash those 80 bytes, then hash the first digest's 32 raw bytes. Hashing a hex string hashes different data.

Header integer fields being little-endian does not change the exercise's big-endian digest interpretation. Keep serialization, hash display, and integer comparison as three separate operations.

5. Bound the search without wraparound

Mine(h, maxNonce) tests 0, 1, ..., maxNonce, returning the first success. It starts at zero regardless of the nonce in h. With maxNonce = 0, one candidate is tested, not zero candidates. At the maximum uint32, incrementing wraps to zero, so terminate after testing the endpoint before incrementing. Return the zero header and false if the bounded search fails.

For intuition only, if 256-bit hashes were independent uniform samples and 0 <= T < 2^256, one candidate succeeds with probability (T+1)/2^256. A larger target makes success easier. This statistical model does not promise success within any fixed nonce interval.

6. Reconstruct on fresh values

Predict the target for 0x020abcde, the canonical bits for target 0x8000, and the number of candidates tested by a failing search with maxNonce = 4.

Check your reasoning

Right-shifting 0x0abcde by eight gives 0x0abc. Target 0x8000 normalizes from exponent 2/mantissa 0x800000 to 0x03008000. The search tests five candidates: 0 through 4 inclusive.

Implement compact targets, then the miner. Test conversion independently before using it to verify hashes. The checkpoint asks you to diagnose truncation, byte order, and search boundaries.


Practice sequence

Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.

Join Discord