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. v3 (collider v1.5.0) is the current wire format; 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 proof-of-walk. 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) and the server replays the forward jumps to confirm the segment links up. Because the challenge is random and issued after commitment, a grinder cannot fabricate a consistent checkpoint chain without actually walking — removing the advantage of submitting cheap off-walk points.

Backwards compatible. The pool runs in compatibility mode by default: existing v3 (v1.5.x) workers 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 challenge check ships disabled (challenge_mode: off) and is turned on only after a v4 client is in the field and the walk step function is validated, so honest workers are never falsely penalized during the rollout.

v3 vs v2 vs v1 -- what changed

VersionShipped inKey changes
3v1.5.0 (current)WORK_ASN grew from 109 to 126 bytes -- adds 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 unambiguous binary modification. The server bans the originating IP permanently on first occurrence, bypassing the rolling-window invalid-DP ladder.

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 negotiated protocol version (the layout itself is unchanged across v1/v2/v3 so a v1 receiver can at least read the type+length and emit a clean MSG_ERROR 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 MUST set this to 3 in v1.5.0. Receivers that read a different value MUST respond MSG_ERROR with code 0x10 (protocol_version_mismatch).
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

NameValueDirectionv3 use
AUTH0x01Client → ServerCarries AuthPayloadV2 (120 B)
AUTH_OK0x02Server → ClientEmpty payload
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 wire size)
DP_SUBMIT0x20--v1 legacy, unused in v3
DP_ACK0x21Server → Clientuint32 count of accepted DPs
DP_BATCH0x22--v1 legacy, unused in v3
DP_SUBMIT_V20x23--Reserved single-DP variant; v3 clients use DP_BATCH_V2 only
DP_BATCH_V20x24Client → Serveruint32 count + N x DistinguishedPointV2 (78 B each)
STATS_REQ0x30Client → ServerEmpty payload
STATS_RSP0x31Server → ClientPoolStats (36 B)
SOLUTION0x40Server → Client ONLYBroadcast after sweep + attestation. v3 clients that EMIT this are server-banned.
PING0x50BothKeepalive (20 s default cadence from client)
PONG0x51BothKeepalive response
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 must be 3 (v1.4.x clients send 2 and are refused with 0x10 protocol_version_mismatch).

WorkAssignment (carried by WORK_ASN 0x11) -- 126 bytes (v3)

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). A single mismatch triggers a permanent IP ban -- there is no warning ladder.

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_V2
};

Connection flow (v3)

Client                                          Server
   |                                              |
   |  -------- TCP / TLS handshake ---------->    |
   |                                              |
   |  -------- AUTH (AuthPayloadV2, flags=3) -->  |
   |  <-------- AUTH_OK ---------------------     |
   |                                              |
   |  -------- WORK_REQ ------------------->      |
   |  <-------- WORK_ASN (WorkAssignment, v3)     |
   |              kangaroo_type = TAME_ONLY or    |
   |              WILD_ONLY (BOTH is illegal)     |
   |                                              |
   |       ... compute DPs of assigned type ...   |
   |                                              |
   |  -------- DP_BATCH_V2 (work_id + seq) -->    |
   |  <-------- 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 != PROTOCOL_VERSION (v3 server sees v1/v2 client)
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_banPermanent on first occurrence. Bypasses the rolling-window ladder. Worker submitting wrong-type DP under TAME_ONLY/WILD_ONLY assignment is unambiguous binary modification, not honest-but-buggy behavior.
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. Carry forward the v3 kangaroo_type / start_offset_* fields in WORK_ASN and the type-mismatch ban behavior or the pool server will mark your implementation as a modified binary on the first wrong-type DP.