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.
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
| Version | Shipped in | Key changes |
|---|---|---|
| 4 | v1.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. |
| 3 | v1.5.0 to 1.5.3 | WORK_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. |
| 2 | v1.4.1 to v1.4.4 | Header 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. |
| 1 | v1.2.x to v1.4.0 | Legacy. 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_typeis1 (TAME_ONLY)or2 (WILD_ONLY). Value0 (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_solutioncodepath 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)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.Message types
| Name | Value | Direction | Use |
|---|---|---|---|
| AUTH | 0x01 | Client → Server | Carries AuthPayloadV2 (120 B) |
| AUTH_OK | 0x02 | Server → Client | For a v4 client, carries AuthOkPayload (324 B) update advert; for a v3 client, zero payload (backward compatible) |
| AUTH_FAIL | 0x03 | Server → Client | Carries MSG_ERROR code (e.g. 0x10 UPGRADE_REQUIRED for v1.4.x clients) |
| WORK_REQ | 0x10 | Client → Server | Empty payload; ask for a chunk |
| WORK_ASN | 0x11 | Server → Client | Carries WorkAssignment (126 B, v3 / v4 wire size) |
| DP_SUBMIT | 0x20 | n/a | v1 legacy, unused |
| DP_ACK | 0x21 | Server → Client | uint32 count of accepted DPs |
| DP_BATCH | 0x22 | n/a | v1 legacy, unused |
| DP_SUBMIT_V2 | 0x23 | n/a | Reserved single-DP variant; clients batch via DP_BATCH_V2 |
| DP_BATCH_V2 | 0x24 | Client → Server | uint32 count + N x DistinguishedPointV2 (78 B each). v3 clients submit DPs here; accepted in compatibility mode and never challenged. |
| DP_SUBMIT_V3 | 0x25 | Client → Server | v4. Single DistinguishedPointV3 (114 B) with the checkpoint-walk commitment |
| DP_BATCH_V3 | 0x26 | Client → Server | v4. uint32 count + N x DistinguishedPointV3 (114 B each) |
| STATS_REQ | 0x30 | Client → Server | Empty payload |
| STATS_RSP | 0x31 | Server → Client | PoolStats (36 B) |
| CHALLENGE | 0x32 | Server → Client | v4. 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_RSP | 0x33 | Client → Server | v4. Reveals endpoint checkpoint distances plus Merkle proofs (variable length, hand coded) |
| SOLUTION | 0x40 | Server → Client ONLY | Broadcast after sweep + attestation. A client that EMITS this is server-banned. |
| PING | 0x50 | Both | Keepalive (20 s default cadence from client) |
| PONG | 0x51 | Both | Keepalive response |
| MAINTENANCE | 0x60 | Server → Client | v4. 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_ERROR | 0xFF | Server → Client | uint8 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_msmust be within+/- 30 sof the server clock (0x12 auth_clock_skewon violation).nonceuniqueness is enforced in a short server-side seen-set (0x13 auth_nonce_reuseon replay).- Header flags carry the client's protocol version (
4in v1.5.4,3for v1.5.0 to 1.5.3). The server negotiates down to the client version; a client below the floor of3(v1.4.x, flags=2) is refused at AUTH with0x10 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_typetracks the RCKangarooKANG_MODE_*enum (third_party/RCKangaroo/defs.h). Distinct fromDistinguishedPointV2.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_bitsmust satisfy8 ≤ 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_idattests 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).sequenceis a per-(worker, work_id) monotonic counter starting at 0 on the first DP of each chunk. Replays of captured DP_BATCHes fail with0x21 dp_sequence_out_of_window.typemust match the worker's assignedkangaroo_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_rootandn_segmentscarry 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_versionand, if older and adownload_urlis present, fetches the binary over HTTPS, verifies it againstsha256, 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)
| Code | Symbol | Meaning |
|---|---|---|
| 0x01 | internal | Server-side fault |
| 0x02 | malformed_header | Bad MAGIC or layout |
| 0x03 | malformed_payload | Payload could not be decoded |
| 0x04 | payload_too_large | Exceeds MAX_MESSAGE_SIZE (1 MB) |
| 0x05 | unsupported_message_type | TYPE byte unknown to server (or SOLUTION from client) |
| 0x10 | protocol_version_mismatch | flags 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. |
| 0x11 | auth_version_unsupported | Legacy 96-byte AuthPayload received |
| 0x12 | auth_clock_skew | timestamp_ms outside +/- 30 s |
| 0x13 | auth_nonce_reuse | Replay of a recently-seen AUTH nonce |
| 0x14 | auth_bad_credentials | Pool password mismatch (when one is configured) |
| 0x20 | work_id_mismatch | DP claims a chunk not assigned to this worker |
| 0x21 | dp_sequence_out_of_window | Replay of an old DP_BATCH (sequence too low) |
| 0x22 | dp_replay | Exact-duplicate DP submitted twice |
| 0x30 | rate_limited | Per-IP DP-rate ceiling tripped |
Anti-cheat thresholds
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.
Server (Python, asyncio)
github.com/hevnsnt/collision-protocolClient (C++, CUDA + Metal)
github.com/hevnsnt/colliderThird-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.