JLP Protocol Specification

protocol_version = 4 · shipped in collider v1.5.4 · v3 (v1.5.x) workers still accepted in compatibility mode · canonical IDL: collision-protocol/protocol/jlp.yaml

Overview

JLP (JeanLucPons protocol) is a binary protocol carried over TCP, optionally wrapped in TLS. It connects Pollard's Kangaroo workers to a pool server and handles authentication, work assignment, distinguished point (DP) submission, statistics, and solution broadcast. v4 (collider v1.5.4) is the current wire format; v3 clients (collider v1.5.0 to 1.5.3) are accepted in compatibility mode, while v1.4.x clients running v1 / v2 are refused at AUTH.

Transport:TCP, optional TLS 1.2 / 1.3
Default Port:17403
Byte Order:Little-endian (header + scalars)
Max Message:1 MB (1,048,576 bytes)
Max DP Batch:10,000 DPs per DP_BATCH_V2
AUTH Window:30 s clock-drift bound

v4 (v1.5.4): checkpoint-replay anti-cheat

v4 adds the proof-of-walk commitment. Each worker commits a Merkle root over the checkpoint distances of its kangaroo walk (one checkpoint every 65,536 jumps) and attaches it to every distinguished point (DP_SUBMIT_V3). The server may then issue a CHALLENGE for a random subset of segments; the worker reveals the endpoint distances plus their Merkle proofs (CHALLENGE_RSP) so the server can replay the forward jumps and confirm the segment links up. The commitment and challenge machinery is in place and carried by every v4 client. The challenge is gated to v4 clients only; a v3 client is never challenged.

Backwards compatible. The pool runs in compatibility mode by default: existing v3 workers (v1.5.0 to 1.5.3) keep mining unchanged, while v4 workers additionally submit checkpoint commitments. Operators switch to strict (v4 required) once every worker has upgraded. Pre-v1.5 (v1.4.x) clients are refused in both modes.

The replay verifier ships off (challenge_mode: off; values are off, shadow, enforce) and is enabled only after the verifier is validated against a real walk, so honest workers are never falsely penalized during the rollout.

v4 vs v3 vs v2 vs v1: what changed

VersionShipped inKey changes
4v1.5.4 (current)AUTH_OK now carries an AuthOkPayload (324 B) update advert that drives silent client auto update. MAINTENANCE (0x60) lets the operator pause workers gracefully. DP_SUBMIT_V3 / DP_BATCH_V3 add a checkpoint-walk Merkle commitment, paired with CHALLENGE / CHALLENGE_RSP (proof-of-walk). Per-connection version negotiation keeps v1.5.0 to 1.5.3 (v3) clients working in compatibility mode.
3v1.5.0 to 1.5.3WORK_ASN grew from 109 to 126 bytes, adding kangaroo_type, start_offset_a, start_offset_b for asymmetric TAME-only / WILD-only assignment. SOLUTION is strictly server-to-client; client never computes the key. v1.4.x clients (flags=2) are refused at AUTH with protocol_version_mismatch.
2v1.4.1 to v1.4.4Header flags byte carries protocol_version (B.5). AUTH grew to AuthPayloadV2 (120 B) with timestamp_ms + nonce for replay defence. DP submission moved to DP_BATCH_V2 (78 B per DP) with work_id attestation + monotonic sequence.
1v1.2.x to v1.4.0Legacy. 96-byte AUTH, 66-byte DPs, no work_id binding, no sequence nonce. Not accepted by the v1.5 server; v1.5 client cannot speak it.

Theft-resistance: TAME / WILD asymmetric assignment

In v1.4.x, a pool worker who happened to find the cross-collision computed the puzzle's private key locally and could sweep the funds before the pool ever saw the solution. v3 closes the window architecturally: each worker runs ONLY tame kangaroos OR ONLY wild kangaroos, the host-side cross-type hashtable is disabled in pool mode, and the pool server is the sole entity where TAME and WILD DPs aggregate.

Worker invariants

  • WORK_ASN.kangaroo_type is 1 (TAME_ONLY) or 2 (WILD_ONLY). Value 0 (BOTH) is reserved and illegal in pool mode; standalone solo mode keeps BOTH.
  • The worker host-side cross-type DP hashtable is disabled. DPs flow straight to the network.
  • The report_solution codepath is removed from the client entirely. There is no client-to-server SOLUTION message in v3.
  • A worker submitting a WILD DP under a TAME assignment (or vice versa) is treated as a recoverable event in v1.5.4, not cheating and not a ban. The server rejects the DP and asks the worker to re request work (the stale work_id path). Only repeated stale or wrong submissions beyond a disconnect limit cause a clean DISCONNECT that forces a fresh AUTH and WORK_REQ, never a permanent IP ban. An epoch race where a worker is reassigned between tame and wild mid flight could emit a stale-type DP with no malice, so banning on it was incorrect.

Per-client-version anti-cheat policy

Every worker gets the asymmetric tame/wild split plus DP shadow verification. v4 workers are additionally eligible for the checkpoint replay challenge when it is enabled; v3 workers are never challenged.

Server flow on collision

1. Match incoming TAME DP against the WILD DP set (and vice versa).
2. Compute k = (d_tame - d_wild + HalfRange) mod n; verify k * G == Q.
3. Sign + broadcast the puzzle-funds sweep transaction via mempool
   provider PRIMARY.
4. Poll mempool provider FALLBACK until the sweep tx is observed
   (cross-provider attestation; hard timeout).
5. ONLY THEN broadcast SOLUTION to every connected worker.

Workers receive the recovered key inside a SOLUTION frame whose puzzle UTXOs are already moving (or already moved). They cannot win the race even by extracting the key from the SOLUTION payload.

Connection URLs

jlp://host:portPlain TCP (testing only)
jlps://host:portTLS (TLS 1.2 / 1.3, SNI + hostname verification, default trust store). Always use this in production.

Message header (8 bytes)

Every JLP message starts with this fixed 8-byte header. The flags byte carries the protocol version (the layout itself is unchanged across v1/v2/v3/v4 so any receiver can at least read the type+length and respond cleanly rather than crash).

+--------+--------+--------+--------+--------+--------+--------+--------+
|   'K'  |   'A'  |   'N'  |   'G'  |  TYPE  | FLAGS  |   LENGTH (u16)    |
+--------+--------+--------+--------+--------+--------+--------+--------+
|  0x4B  |  0x41  |  0x4E  |  0x47  |  0xNN  |  0xVV  |  LO     |  HI    |
+--------+--------+--------+--------+--------+--------+--------+--------+
|<-------- 4 bytes -------->|<- 1B ->|<- 1B ->|<------ 2 bytes -------->|
|         MAGIC "KANG"      |  TYPE  | flags  |   payload_size (LE)     |
                                       (= protocol_version)
MAGIC4 bytes - ASCII "KANG" (0x4B 0x41 0x4E 0x47). Any mismatch immediately closes the connection.
TYPE1 byte - MessageType enum (table below).
FLAGS1 byte - protocol_version. Senders set this to their own protocol version (4 in v1.5.4). The server negotiates per connection: at AUTH it reads the client flags and sets negotiated_version = min(client_flags, 4), then stamps that version into the flags byte of every outbound frame, so a v3 client (flags=3) is answered with flags=3 and a v4 client with flags=4. A client whose flags fall below the security floor of 3 is refused at AUTH with 0x10 (protocol_version_mismatch); the mismatch error now applies only below the floor.
payload_size2 bytes - little-endian u16, payload byte count following the header. Total message bytes therefore capped at 8 + 65535. Larger DP batches are split across multiple DP_BATCH_V2 frames.

Message types

NameValueDirectionUse
AUTH0x01Client → ServerCarries AuthPayloadV2 (120 B)
AUTH_OK0x02Server → ClientFor a v4 client, carries AuthOkPayload (324 B) update advert; for a v3 client, zero payload (backward compatible)
AUTH_FAIL0x03Server → ClientCarries MSG_ERROR code (e.g. 0x10 UPGRADE_REQUIRED for v1.4.x clients)
WORK_REQ0x10Client → ServerEmpty payload; ask for a chunk
WORK_ASN0x11Server → ClientCarries WorkAssignment (126 B, v3 / v4 wire size)
DP_SUBMIT0x20n/av1 legacy, unused
DP_ACK0x21Server → Clientuint32 count of accepted DPs
DP_BATCH0x22n/av1 legacy, unused
DP_SUBMIT_V20x23n/aReserved single-DP variant; clients batch via DP_BATCH_V2
DP_BATCH_V20x24Client → Serveruint32 count + N x DistinguishedPointV2 (78 B each). v3 clients submit DPs here; accepted in compatibility mode and never challenged.
DP_SUBMIT_V30x25Client → Serverv4. Single DistinguishedPointV3 (114 B) with the checkpoint-walk commitment
DP_BATCH_V30x26Client → Serverv4. uint32 count + N x DistinguishedPointV3 (114 B each)
STATS_REQ0x30Client → ServerEmpty payload
STATS_RSP0x31Server → ClientPoolStats (36 B)
CHALLENGE0x32Server → Clientv4. Checkpoint-replay challenge (variable length): work_id u64 LE, nonce 8 B, count u16 LE, then count x segment index u32 LE. Gated to v4 clients only.
CHALLENGE_RSP0x33Client → Serverv4. Reveals endpoint checkpoint distances plus Merkle proofs (variable length, hand coded)
SOLUTION0x40Server → Client ONLYBroadcast after sweep + attestation. A client that EMITS this is server-banned.
PING0x50BothKeepalive (20 s default cadence from client)
PONG0x51BothKeepalive response
MAINTENANCE0x60Server → Clientv4. Carries MaintenancePayload (262 B); tells the worker to back off and auto resume. A v3 client instead gets an AUTH_FAIL carrying a maintenance note.
MSG_ERROR0xFFServer → Clientuint8 error code (table below)

Named MSG_ERROR (not ERROR) to dodge the Windows ERROR macro.

Payload structures

AuthPayloadV2 (carried by AUTH 0x01): 120 bytes

struct pack format: <64s32sQ16s

struct AuthPayloadV2 {
    char     worker_name[64];   // UTF-8 worker name / BTC payout address
    char     password[32];      // Optional pool password (null-padded)
    uint64_t timestamp_ms;      // Client wall-clock time in ms (little-endian)
    uint8_t  nonce[16];         // Per-AUTH random nonce
};
  • timestamp_ms must be within +/- 30 s of the server clock (0x12 auth_clock_skew on violation).
  • nonce uniqueness is enforced in a short server-side seen-set (0x13 auth_nonce_reuse on replay).
  • Header flags carry the client's protocol version (4 in v1.5.4, 3 for v1.5.0 to 1.5.3). The server negotiates down to the client version; a client below the floor of 3 (v1.4.x, flags=2) is refused at AUTH with 0x10 protocol_version_mismatch.

WorkAssignment (carried by WORK_ASN 0x11): 126 bytes (v3 / v4)

struct pack format: <33s32s32sIQBQQ

struct WorkAssignment {
    uint8_t  public_key[33];    // Compressed secp256k1 target pubkey
    uint8_t  range_start[32];   // Big-endian 32-byte chunk start
    uint8_t  range_end[32];     // Big-endian 32-byte chunk end
    uint32_t dp_bits;           // Distinguished-point bit threshold (LE)
    uint64_t work_id;           // Low 64 bits of the chunk identifier (LE)

    // v3 additions
    uint8_t  kangaroo_type;     // 1=TAME_ONLY, 2=WILD_ONLY (0=BOTH reserved)
    uint64_t start_offset_a;    // Inclusive low 64 bits of worker's offset window
    uint64_t start_offset_b;    // Exclusive upper bound (b > a)
};
  • kangaroo_type tracks the RCKangaroo KANG_MODE_*enum (third_party/RCKangaroo/defs.h). Distinct from DistinguishedPointV2.type, which is the per-DP tame/wild byte (0=tame, 1=wild).
  • [start_offset_a, start_offset_b)is the worker's disjoint sub-window inside [range_start, range_end). Server guarantees no two same-type workers overlap.
  • Pre-existing fields (public_key … work_id) keep their identical byte offsets and little-endian encoding. v1.4.x decoders see a 17-byte-too-long payload and reject it as malformed; old and new editions cannot accidentally interoperate.
  • dp_bits must satisfy 8 ≤ dp_bits ≤ 32. A client that receives a value outside this range MUST disconnect (anti-DoS check; a buggy or malicious dp_bits=255 would otherwise burn GPU cycles indefinitely).

DistinguishedPointV2 (carried by DP_BATCH_V2 0x24): 78 bytes per DP

struct pack format: <QI32s32sBB

struct DistinguishedPointV2 {
    uint64_t work_id;           // Chunk id from the active WORK_ASN
    uint32_t sequence;          // Per-(worker, work_id) monotonic counter
    uint8_t  x[32];             // Big-endian X coordinate
    uint8_t  d[32];             // Big-endian walked distance
    uint8_t  type;              // 0=tame, 1=wild
    uint8_t  dp_bits;           // Leading-zero bit count required of x
};

struct DPBatchV2 {
    uint32_t count;             // 1 .. MAX_BATCH_SIZE (10,000)
    DistinguishedPointV2 dps[count];
};

// Wire size: 4 + (count * 78) bytes
// Server rejects any payload >= 1 MB (MAX_MESSAGE_SIZE).
  • work_id attests which chunk this DP came from. The server rejects any DP whose claimed work_id mismatches the worker's current assignment (0x20 work_id_mismatch).
  • sequence is a per-(worker, work_id) monotonic counter starting at 0 on the first DP of each chunk. Replays of captured DP_BATCHes fail with 0x21 dp_sequence_out_of_window.
  • type must match the worker's assigned kangaroo_type (TAME_ONLY workers emit type=0 only; WILD_ONLY workers emit type=1 only). As of v1.5.4 a wrong-type DP is treated as a recoverable event, not cheating and not a ban: the server rejects the DP and asks the worker to re request work (the stale work_id path). Only repeated stale or wrong submissions beyond a disconnect limit force a clean DISCONNECT (fresh AUTH + WORK_REQ), never a permanent IP ban. An epoch race where a worker is reassigned between tame and wild mid flight can emit a stale-type DP with no malice, so banning on it was incorrect.

DistinguishedPointV3 (carried by DP_SUBMIT_V3 0x25 / DP_BATCH_V3 0x26): 114 bytes per DP (v4)

struct pack format: <QI32s32sBB32sI

struct DistinguishedPointV3 {
    uint64_t work_id;           // Chunk id from the active WORK_ASN
    uint32_t sequence;          // Per-(worker, work_id) monotonic counter
    uint8_t  x[32];             // Big-endian X coordinate
    uint8_t  d[32];             // Big-endian walked distance
    uint8_t  type;              // 0=tame, 1=wild
    uint8_t  dp_bits;           // Leading-zero bit count required of x
    uint8_t  ckpt_root[32];     // Merkle root over the walk's checkpoint
                                //   distances (one checkpoint every
                                //   CHECKPOINT_INTERVAL = 65536 jumps)
    uint32_t n_segments;        // Committed segment count (checkpoint_count - 1)
};

struct DPBatchV3 {
    uint32_t count;             // 1 .. MAX_BATCH_SIZE (10,000)
    DistinguishedPointV3 dps[count];
};

// Wire size: 4 + (count * 114) bytes
  • A v2 superset plus the checkpoint-walk commitment. The first six fields are byte-identical to DistinguishedPointV2; ckpt_root and n_segments carry the proof-of-walk commitment.
  • In compatibility mode the server also accepts the older DP_BATCH_V2 frames and credits them identically. A v2 frame is simply never challenged.

AuthOkPayload (carried by AUTH_OK 0x02 to v4 clients): 324 bytes (v4)

struct pack format: <16s16sB3s256s32s

struct AuthOkPayload {
    uint8_t latest_version[16]; // ASCII semver of the latest client,
                                //   null-padded (e.g. "1.5.4")
    uint8_t min_version[16];    // ASCII semver of the minimum supported client
    uint8_t flags;              // bit0 = update_available,
                                //   bit1 = maintenance_active
    uint8_t reserved[3];
    uint8_t download_url[256];  // HTTPS URL of the latest signed binary,
                                //   null-padded; all-zero disables auto update
    uint8_t sha256[32];         // Raw SHA-256 of the binary at download_url
};
  • This drives in-band client auto update. The client compares its own version to latest_version and, if older and a download_url is present, fetches the binary over HTTPS, verifies it against sha256, then replaces itself and relaunches.
  • A v3 client receives a zero-payload AUTH_OK (the v3 form), so the advert is fully backward compatible.

MaintenancePayload (carried by MAINTENANCE 0x60): 262 bytes (v4)

struct pack format: <BBI256s

struct MaintenancePayload {
    uint8_t  active;            // 1 = maintenance in effect (back off),
                                //   0 = all clear (resume)
    uint8_t  reserved;
    uint32_t retry_after_secs;  // Suggested base reconnect backoff in seconds
                                //   (LE); client adds jitter
    uint8_t  message[256];      // Operator note, null-padded ASCII
};
  • The server sends this after AUTH_OK to a worker that connects while the pool is in maintenance, or broadcasts it to live workers when an operator toggles maintenance on. The client shows the note and backs off gracefully, then auto resumes.
  • A v3 client (which has no 0x60 frame) instead receives an AUTH_FAIL carrying a maintenance note.

PoolStats (carried by STATS_RSP 0x31): 36 bytes

struct pack format: <QIIffQI

struct PoolStats {
    uint64_t total_dps;         // Cumulative pool DP count
    uint32_t total_workers;     // Distinct worker names ever seen
    uint32_t active_workers;    // Workers active in last 5 minutes
    float    dps_per_second;    // Pool aggregate DP rate
    float    your_share;        // Fraction of total_dps for this worker name
    uint64_t your_dps;          // DPs credited to this worker name
    uint32_t uptime_seconds;    // Server uptime
};

DP_ACK (0x21): 4 bytes

struct DPAck {
    uint32_t count;             // Number of DPs accepted from the last DP batch
};

Connection flow (v4)

Client                                          Server
   |                                              |
   |  -------- TCP / TLS handshake ---------->    |
   |                                              |
   |  -------- AUTH (AuthPayloadV2, flags=4) -->  |
   |  <-------- AUTH_OK (AuthOkPayload advert)    |
   |             negotiated_version = min(4, 4)   |
   |                                              |
   |  (a v1.5.0 to 1.5.3 client sends flags=3,    |
   |   is negotiated down, and is answered in v3: |
   |   flags=3 + zero-payload AUTH_OK)            |
   |                                              |
   |  -------- WORK_REQ ------------------->      |
   |  <-------- WORK_ASN (WorkAssignment, v4)     |
   |              kangaroo_type = TAME_ONLY or    |
   |              WILD_ONLY (BOTH is illegal)     |
   |                                              |
   |       ... compute DPs of assigned type ...   |
   |                                              |
   |  -------- DP_BATCH_V3 (work_id + seq +       |
   |              ckpt_root commitment) -->       |
   |  <-------- DP_ACK ----------------------     |
   |                                              |
   |  -------- STATS_REQ ----------------->       |
   |  <-------- STATS_RSP (PoolStats) -------     |
   |                                              |
   |  -------- PING (every 20 s) ---------->      |
   |  <-------- PONG -----------------------      |
   |                                              |
   |             [ collision found server-side ]  |
   |             [ sweep tx broadcast, attested ] |
   |  <-------- SOLUTION (server-only) ------     |
   |                                              |
   |       Client confirms puzzle solved; no      |
   |       local key recovery, no key handling.   |

Error codes (MSG_ERROR payload)

CodeSymbolMeaning
0x01internalServer-side fault
0x02malformed_headerBad MAGIC or layout
0x03malformed_payloadPayload could not be decoded
0x04payload_too_largeExceeds MAX_MESSAGE_SIZE (1 MB)
0x05unsupported_message_typeTYPE byte unknown to server (or SOLUTION from client)
0x10protocol_version_mismatchflags below the security floor of 3 (server sees a v1.4.x or older client). Applies only below the floor; v3 and v4 are negotiated, not rejected.
0x11auth_version_unsupportedLegacy 96-byte AuthPayload received
0x12auth_clock_skewtimestamp_ms outside +/- 30 s
0x13auth_nonce_reuseReplay of a recently-seen AUTH nonce
0x14auth_bad_credentialsPool password mismatch (when one is configured)
0x20work_id_mismatchDP claims a chunk not assigned to this worker
0x21dp_sequence_out_of_windowReplay of an old DP_BATCH (sequence too low)
0x22dp_replayExact-duplicate DP submitted twice
0x30rate_limitedPer-IP DP-rate ceiling tripped

Anti-cheat thresholds

invalid_dp_threshold100 invalid DPs per IP per rolling 1 h window before a ban tier escalates.
ban_durations1 h → 6 h → 1 d → 7 d → permanent (over a 30-day ban-count window).
type_mismatch_banRecoverable as of v1.5.4 (no longer a ban). A wrong-type DP under a TAME_ONLY / WILD_ONLY assignment is rejected and the worker is asked to re request work; only repeated stale or wrong submissions beyond a disconnect limit force a clean DISCONNECT (fresh AUTH + WORK_REQ), never a permanent IP ban. A tame/wild epoch race can emit a stale-type DP with no malice, so banning on it was incorrect.
auth_clock_drift+/- 30 s on AUTH timestamp_ms.
dp_verify_default_modeshadow (server samples DPs for cryptographic re-check at random; off / shadow / enforce configurable per deployment).

Reference implementations

Authoritative IDL

All wire structs above are emitted from a single YAML source by a codegen tool. C++ packed structs (with compile-time size asserts) and Python dataclasses (with to_bytes / from_bytes) are generated from the same definition; hand-editing the generated files is rejected by CI.

collision-protocol/protocol/jlp.yaml

Server (Python, asyncio)

github.com/hevnsnt/collision-protocol

Client (C++, CUDA + Metal)

github.com/hevnsnt/collider

Third-party client implementations are welcome in any language with TCP socket support; the IDL is the single source of truth. Set the header flags byte to your protocol version (3 or 4); the server negotiates per connection and answers you in your own version. Carry forward the v3 kangaroo_type / start_offset_* fields in WORK_ASN and emit only the DP type matching your assignment. A v4 client additionally commits a checkpoint-walk Merkle root on every DP and answers CHALLENGE frames; a v3 client submits DP_BATCH_V2 and is never challenged.